本版本优化点,
- 添加更多测试用例。
- 优化CMakeLists.txt文件编译流程,先生成一个动态链接库,再让源文件都链接到动态库,这样就只需要编译一次依赖库了。节省一半的编译时间。
- 优化CMakeLists.txt文件,测试文件分离,一个可执行文件只绑定到一个测试用例的cpp文件。
- 添加时间戳到时间的转换方法,以及时间到时间戳的转换方法。
程序目录结构如下,
CMakeLists.txt文件如下,
cmake_minimum_required(VERSION 2.6)
if(APPLE)
message(STATUS "This is Apple, do nothing.")
elseif(UNIX)
message(STATUS "This is linux, set CMAKE_PREFIX_PATH.")
set(CMAKE_PREFIX_PATH /vcpkg/ports/cppwork/vcpkg_installed/x64-linux/share)
endif(APPLE)
project(app_perf_test)
add_definitions(-std=c++17)
add_definitions(-g)
find_package(ZLIB)
find_package(glog REQUIRED)
find_package(OpenCV REQUIRED )
find_package(Boost REQUIRED COMPONENTS
system
filesystem
serialization
program_options
thread
)
find_package(DataFrame REQUIRED)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set INCLUDE_DIRS")
set(INCLUDE_DIRS ${Boost_INCLUDE_DIRS} /usr/local/include /usr/local/iODBC/include /opt/snowflake/snowflakeodbc/include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set INCLUDE_DIRS")
set(INCLUDE_DIRS ${Boost_INCLUDE_DIRS} /usr/local/include ${CMAKE_CURRENT_SOURCE_DIR}/../../)
endif(APPLE)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set LINK_DIRS")
set(LINK_DIRS /usr/local/lib /usr/local/iODBC/lib /opt/snowflake/snowflakeodbc/lib/universal)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set LINK_DIRS")
set(LINK_DIRS ${Boost_INCLUDE_DIRS} /usr/local/lib /vcpkg/ports/cppwork/vcpkg_installed/x64-linux/lib)
endif(APPLE)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set ODBC_LIBS")
set(ODBC_LIBS iodbc iodbcinst)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set LINK_DIRS")
set(ODBC_LIBS odbc odbcinst ltdl)
endif(APPLE)
include_directories(${INCLUDE_DIRS})
LINK_DIRECTORIES(${LINK_DIRS})
file( GLOB test_file_list ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../utils/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../cases/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/../../http/impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../yaml/impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../df/impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../death_handler/impl/*.cpp)
add_library(${PROJECT_NAME}_lib SHARED ${APP_SOURCES} ${test_file})
target_link_libraries(${PROJECT_NAME}_lib ${Boost_LIBRARIES} ZLIB::ZLIB glog::glog DataFrame::DataFrame ${OpenCV_LIBS})
target_link_libraries(${PROJECT_NAME}_lib ssl crypto libgtest.a pystring libyaml-cpp.a libgmock.a ${ODBC_LIBS} libnanodbc.a pthread dl backtrace)
foreach( test_file ${test_file_list} )
file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${test_file})
string(REPLACE ".cpp" "" file ${filename})
add_executable(${file} ${test_file})
target_link_libraries(${file} ${PROJECT_NAME}_lib)
endforeach( test_file ${test_file_list})
test_cfg.h
#ifndef _FREDRIC_TEST_CFG_H_
#define _FREDRIC_TEST_CFG_H_
#include <string>
#include <map>
#include <vector>
extern std::string v2_query_path;
extern int max_compare_count;
extern std::string www_host;
extern std::string api_host;
extern std::string www_company_path;
extern std::map<std::string, std::string> www_headers;
extern std::map<std::string, std::string> api_headers;
extern bool use_file;
// App Performance related json files
extern std::string apps_meta_file;
extern std::string download_and_revenue;
extern std::string usage_file;
extern std::string demographics_age_file;
extern std::string demographics_gender_file;
extern std::string retention_file;
extern std::string test_dim_parse_data_file;
extern std::vector<std::string> common_keys;
#endif
impl/test_cfg.cpp
#include "api_accuracy/test_cfg.h"
std::string api_host = "tai4-api.appannie.com";
std::string www_host = "api2.appannie.com";
std::string v2_query_path = "/v2/query";
int max_compare_count = 10;
std::map<std::string, std::string> www_headers{
{"Authorization", "Bearer {YOUR_BEAR_TOKEN}"},
{"Content-Type", "application/json"}
};
std::map<std::string, std::string> api_headers{
{"Authorization", "Bearer {YOUR_BEAR_TOKEN}"},
{"Content-Type", "application/json"}
};
bool use_file = true;
std::string apps_meta_file = "../datas/app_perf/apps_meta_data.json";
std::string test_dim_parse_data_file = "../datas/app_perf/test_dim_parse_data.json";
std::string download_and_revenue = "../datas/app_perf/download_and_revenue.json";
std::string usage_file = "../datas/app_perf/usage.json";
std::string demographics_age_file = "../datas/app_perf/demographics_age.json";
std::string demographics_gender_file = "../datas/app_perf/demographics_gender.json";
std::string retention_file = "../datas/app_perf/retention.json";
std::vector<std::string> common_keys {"date", "country_code", "device_code"};
utils/req.h
#ifndef _FREDRIC_REQ_H_
#define _FREDRIC_REQ_H_
#include <string>
struct Req {
static std::string make_a_www_query(const std::string& path, const std::string& body);
static std::string make_a_api_query(const std::string& path);
};
#endif
utils/req.cpp
#include "api_accuracy/utils/req.h"
#include "http/http_util.h"
#include "api_accuracy/test_cfg.h"
#include <glog/logging.h>
std::string Req::make_a_www_query(const std::string& path, const std::string& body) {
std::string www_res{};
bool get_www_res = HttpUtil::post_and_get_str(www_host, path, www_headers, body, www_res);
if(!get_www_res) {
LOG(ERROR) << "Get WWW result failure" << "\n";
return "";
}
return std::move(www_res);
}
std::string Req::make_a_api_query(const std::string& path) {
std::string api_res{};
bool get_api_res = HttpUtil::get_str(api_host, path, api_headers, api_res);
if(!get_api_res) {
LOG(ERROR) << "Get api result failure" << "\n";
return "";
}
return std::move(api_res);
}
utils/io_util.h
#ifndef _FREDRIC_IO_UTIL_H_
#define _FREDRIC_IO_UTIL_H_
#include <string>
struct IOUtil {
static std::string read_file(const std::string& file_name);
static bool write_file(const std::string& file_name, const std::string& content);
};
#endif
utils/io_util.cpp
#include "api_accuracy/utils/io_util.h"
#include <glog/logging.h>
#include <fstream>
std::string IOUtil::read_file(const std::string& file_name) {
std::fstream fs{file_name};
if(!fs.is_open()) {
LOG(ERROR) << "Open file [" << file_name << "] failed" << "\n";
return "";
}
std::stringstream ss {};
ss << fs.rdbuf();
return std::move(ss.str());
}
bool IOUtil::write_file(const std::string& file_name, const std::string& content) {
std::fstream fs{file_name, std::ios::out | std::ios::trunc};
if(!fs.is_open()) {
LOG(ERROR) << "Open file [" << file_name << "] failed" << "\n";
return false;
}
fs << content;
fs.close();
return true;
}
utils/funcs.h
#ifndef _FREDRIC_FUNCS_H_
#define _FREDRIC_FUNCS_H_
#include "json/json.hpp"
#include <vector>
#include <string>
#include <map>
using json = nlohmann::json;
std::vector<std::string> get_map_keys(const std::map<std::string, std::string>& field_mapping);
std::vector<std::string> get_map_values(const std::map<std::string, std::string>& field_mapping);
std::string read_query_file_and_replace_consts(const json& prod, const std::string& www_query_file_name);
json parse_dim_product_fields(const json& result_json, const std::vector<std::string>& dim_keys_);
json convert_dim_values_to_api_values(const json& values, const std::map<std::string, std::string>& api_dim_mapping);
bool find_diff_and_save_f(const json& values_, const json& act_www_values_, const std::string& api_data_field_name);
std::string timestamp_to_utc_time(const uint64_t& timestamp_);
time_t utc_timestamp_from_string(const std::string& date_str);
float get_float_fraction(float number);
#endif
utils/funcs.cpp
#include "api_accuracy/utils/funcs.h"
#include <pystring/pystring.h>
#include <algorithm>
#include <cstddef>
#include <ctime>
#include <locale>
#include <iomanip>
#include <sstream>
#include <math.h>
#include "api_accuracy/utils/io_util.h"
#include "date/date.h"
#include "df/df.h"
#include "glog/logging.h"
std::vector<std::string> get_map_keys(
const std::map<std::string, std::string>& field_mapping) {
std::vector<std::string> keys{};
std::for_each(field_mapping.begin(), field_mapping.end(),
[&keys](const auto& item) { keys.push_back(item.first); });
return keys;
}
std::vector<std::string> get_map_values(
const std::map<std::string, std::string>& field_mapping) {
std::vector<std::string> values{};
std::for_each(
field_mapping.begin(), field_mapping.end(),
[&values](const auto& item) { values.push_back(item.second); });
return values;
}
std::string read_query_file_and_replace_consts(
const json& prod, const std::string& www_query_file_name) {
auto product_id = prod["product_id"].get<int64_t>();
auto market_code = prod["market_code"].get<std::string>();
auto start_date = prod["start_date"].get<std::string>();
auto end_date = prod["end_date"].get<std::string>();
auto country_code = prod["countries"].get<std::string>();
auto device_code = prod["devices"].get<std::string>();
auto granularity = prod["granularity"].get<std::string>();
auto file_content = IOUtil::read_file(www_query_file_name);
file_content = pystring::replace(file_content, "${product_id}",
std::to_string(product_id));
file_content =
pystring::replace(file_content, "${market_code}", market_code);
file_content = pystring::replace(file_content, "${start_date}", start_date);
file_content = pystring::replace(file_content, "${end_date}", end_date);
std::vector<std::string> country_list{};
pystring::split(country_code, country_list, ",");
json country_js = country_list;
file_content =
pystring::replace(file_content, "${country_code}", country_js.dump());
file_content =
pystring::replace(file_content, "${granularity}", granularity);
if (country_list[0] == "all_supported") {
auto content_js = json::parse(file_content);
content_js["filters"].erase("country_code");
file_content = content_js.dump();
}
std::vector<std::string> device_list{};
pystring::split(device_code, device_list, ",");
if (device_list[0] != "all_supported") {
auto content_js = json::parse(file_content);
content_js["filters"]["device_code"] = {{"in", device_list}};
file_content = content_js.dump();
}
return file_content;
}
json parse_dim_product_fields(const json& result_json,
const std::vector<std::string>& dim_keys_) {
json values;
auto facets = result_json["data"]["facets"];
auto dims = result_json["data"]["dimensions"];
for (auto&& facet_ : facets) {
json value;
for (auto&& dim_key_ : dim_keys_) {
if (dim_key_.find("/") == std::string::npos) {
value[dim_key_] = facet_[dim_key_];
} else {
json tmp_val;
std::string tmp_key = "";
std::vector<std::string> one_dim_keys{};
pystring::split(dim_key_, one_dim_keys, "/");
for (int i = 0; i < one_dim_keys.size(); ++i) {
if (i == 0) {
tmp_key = one_dim_keys[0];
if (!facet_[tmp_key].is_null()) {
tmp_val =
std::to_string(facet_[tmp_key].get<int64_t>());
} else {
break;
}
} else {
for (auto &&begin = dims.begin(), end = dims.end();
begin != end; ++begin) {
auto in_dim_key = begin.key();
auto in_dim_val = begin.value();
if (one_dim_keys[i - 1] == in_dim_key) {
for (auto &&ii_begin = in_dim_val.begin(),
ii_end = in_dim_val.end();
ii_begin != ii_end; ++ii_begin) {
auto ii_dim_key = ii_begin.key();
auto ii_dim_val = ii_begin.value();
if (ii_dim_key ==
tmp_val.get<std::string>()) {
if (i == (one_dim_keys.size() - 1)) {
tmp_val =
ii_dim_val[one_dim_keys[i]];
} else {
if (!ii_dim_val[one_dim_keys[i]]
.is_null()) {
tmp_val = std::to_string(
ii_dim_val[one_dim_keys[i]]
.get<int64_t>());
} else {
break;
}
}
}
}
}
}
}
}
value[dim_key_] = tmp_val;
}
}
values.emplace_back(std::move(value));
}
return std::move(values);
}
json convert_dim_values_to_api_values(
const json& values,
const std::map<std::string, std::string>& api_dim_mapping) {
json new_values;
for (auto&& value : values) {
json new_value;
for (auto&& api_dim_item : api_dim_mapping) {
auto api_key = api_dim_item.first;
auto dim_key = api_dim_item.second;
new_value[api_key] = value[dim_key];
}
new_values.emplace_back(std::move(new_value));
}
return std::move(new_values);
}
bool find_diff_and_save_f(const json& values_, const json& act_www_values_,
const std::string& api_data_field_name) {
std::vector<std::string> pri_keys_{};
auto df1 = df_op::convert_json_to_df(values_, pri_keys_);
auto df2 = df_op::convert_json_to_df(act_www_values_, pri_keys_);
std::string res_csv_path = "../result/" + api_data_field_name + "_res.csv";
if (values_.size() == 0 && act_www_values_.size() == 0) {
return false;
} else if (values_.size() == 0 && act_www_values_.size() != 0) {
df_op::write_to_csv(df2, res_csv_path);
return true;
} else if (values_.size() != 0 && act_www_values_.size() == 0) {
df_op::write_to_csv(df1, res_csv_path);
return true;
} else {
auto df3 =
df1.concat<decltype(df2), json>(df2, concat_policy::all_columns);
std::vector<std::string> keys_{Keys};
auto df4 = df_op::remove_duplicate(df3, keys_);
auto df_rows = df4.shape().first;
if (df_rows == 0) {
return false;
}
df_op::write_to_csv(df3, res_csv_path);
return true;
}
}
std::string timestamp_to_utc_time(const uint64_t& timestamp_) {
char mbstr[100];
long gmt_time = (long)((double)timestamp_ / (double)1000);
size_t size =
std::strftime(mbstr, sizeof(mbstr), "%Y-%m-%d", std::gmtime(&gmt_time));
if (size) {
return mbstr;
}
return "";
}
// Return timestamp as seconds
time_t utc_timestamp_from_string(const std::string& date_str) {
using namespace date;
std::istringstream in{date_str};
sys_days tp;
in >> parse("%F", tp);
auto time = tp.time_since_epoch();
auto time_stamp = std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
time_stamp = (time_t)((double)time_stamp/(double)1000);
return time_stamp;
}
float get_float_fraction(float number) {
float int_part;
float fractpart = modf (number, &int_part);
return fractpart;
}
utils/compare.h
#ifndef _FREDRIC_COMPARE_H_
#define _FREDRIC_COMPARE_H_
#include "api_accuracy/utils/beans.h"
#include <json/json.hpp>
#include <map>
#include <string>
using json = nlohmann::json;
struct Compare {
Compare(const json& app_perf_result);
json get_api_products_fields(const std::map<std::string, std::string>& field_mapping);
json get_www_product_fields(const json& prod, const std::string& www_request_file_name, const std::map<std::string, std::string>& field_mapping);
bool compare_api_and_www_meta_equal(const std::map<std::string, std::string>& field_mappings, const std::string& www_query_file);
bool compare_api_and_www_equal(const std::map<std::string, std::string>& field_mappings, const std::string& api_data_field_name, const std::string& result_file_name, const std::string& www_query_file);
bool compare_api_and_www_values_equal(const std::map<std::string, std::string>& field_mappings, const std::string& api_data_field_name, const std::vector<std::string>& www_query_file_list);
bool compare_api_and_www_retention_equal(const std::map<std::string, RetentionValue>& field_mappings, const std::string& api_data_field_name, const std::string& result_file_name, const std::string& www_query_file);
json get_api_values(const std::map<std::string, std::string>& field_mappings, const std::string& data_field_name);
json get_one_www_value(const json& prod, const std::string& www_request_file_name, const std::map<std::string, std::string>& field_mappings);
json get_retention_www_values(const json& prod, const std::string& www_request_file_name, const RetentionValue& rent_value);
json app_perf_result_;
};
#endif
utils/compare.cpp
#include "api_accuracy/utils/compare.h"
#include "api_accuracy/utils/funcs.h"
#include "api_accuracy/utils/req.h"
#include "api_accuracy/test_cfg.h"
#include <pystring/pystring.h>
#include "glog/logging.h"
#include <algorithm>
Compare::Compare(const json& app_perf_result) {
app_perf_result_ = app_perf_result;
}
json Compare::get_api_products_fields(const std::map<std::string, std::string>& field_mapping) {
auto start_date = app_perf_result_["start_date"];
auto end_date = app_perf_result_["end_date"];
auto countries = app_perf_result_["countries"];
auto granularity = app_perf_result_["granularity"];
auto devices = app_perf_result_["devices"];
auto products = app_perf_result_["products"];
auto api_fields = get_map_keys(field_mapping);
json api_values {};
for(auto&& product_: products) {
json api_value {};
auto product_id = product_["product_id"];
auto market_code = product_["market_code"];
json prod_{
{"product_id", product_id},
{"market_code", market_code},
{"granularity", granularity},
{"start_date", start_date},
{"end_date", end_date},
{"countries", countries},
{"devices", devices}
};
api_value["product"] = prod_;
json values {};
json value {};
for(auto api_field: api_fields) {
value[api_field] = product_[api_field];
}
values.emplace_back(std::move(value));
api_value["values"] = values;
api_values.push_back(std::move(api_value));
}
return api_values;
}
json Compare::get_www_product_fields(const json& prod, const std::string& www_request_file_name, const std::map<std::string, std::string>& field_mapping) {
auto dar_query_str = read_query_file_and_replace_consts(prod, www_request_file_name);
LOG(ERROR) << dar_query_str << "\n";
auto dar_query_res_str = Req::make_a_www_query(v2_query_path, dar_query_str);
auto dar_query_res = json::parse(dar_query_res_str);
auto field_values = get_map_values(field_mapping);
auto www_query_tmp_res = parse_dim_product_fields(dar_query_res, field_values);
auto www_query_res = convert_dim_values_to_api_values(www_query_tmp_res, field_mapping);
return www_query_res;
}
bool Compare::compare_api_and_www_meta_equal(const std::map<std::string, std::string>& field_mappings, const std::string& www_query_file) {
auto api_products_ = get_api_products_fields(field_mappings);
bool is_value_equal = true;
int i = 0;
for(auto&& api_prod_: api_products_) {
auto prod_ = api_prod_["product"];
auto values_ = api_prod_["values"];
++i;
// 对比前max_compare_count条,退出
if(i > max_compare_count) {
LOG(INFO) << "Hit " << max_compare_count << " records, return." << "\n";
break;
}
auto www_values_ = get_www_product_fields(prod_, www_query_file, field_mappings);
LOG(INFO) << values_.dump() << "\n";
LOG(INFO) << www_values_.dump() << "\n";
std::sort(values_.begin(), values_.end());
std::sort(www_values_.begin(), www_values_.end());
bool has_diff = find_diff_and_save_f(values_, www_values_, "www_meta_info");
is_value_equal = !has_diff;
if(!is_value_equal) {
LOG(ERROR) << "API and www values are not equals\n";
LOG(ERROR) << "API : www_meta_info\n";
LOG(ERROR) << values_ << "\n";
LOG(ERROR) << "WWW : www_meta_info\n";
LOG(ERROR) << www_values_ << "\n";
break;
}
}
return is_value_equal;
}
json Compare::get_api_values(const std::map<std::string, std::string>& field_mappings, const std::string& data_field_name) {
auto products_ = app_perf_result_["products"];
auto start_date = app_perf_result_["start_date"];
auto end_date = app_perf_result_["end_date"];
auto countries = app_perf_result_["countries"];
auto devices = app_perf_result_["devices"];
auto granularity = app_perf_result_["granularity"];
json api_values;
for(auto&& product_ : products_) {
json product_and_values;
auto product_id = product_["product_id"];
auto market_code = product_["market_code"];
auto datas = product_[data_field_name];
json prod_;
prod_["product_id"] = product_id;
prod_["market_code"] = market_code;
prod_["granularity"] = granularity;
prod_["start_date"] = start_date;
prod_["end_date"] = end_date;
prod_["countries"] = countries;
prod_["devices"] = devices;
product_and_values["product"] = prod_;
json values;
for(auto&& data : datas) {
json value;
for(auto&& key_map_item: field_mappings) {
auto api_key = key_map_item.first;
if(data[api_key].is_number_float() && (get_float_fraction(data[api_key].get<float>()) == 0.0)) {
value[api_key] = (uint64_t)(data[api_key].get<float>());
}else {
value[api_key] = data[api_key];
}
}
bool should_append = false;
for(auto&& begin=value.begin(), end=value.end(); begin!=end; ++begin) {
auto key = begin.key();
auto key_it = std::find(common_keys.begin(), common_keys.end(), key);
// 没找着这个key
if(key_it == common_keys.end()) {
if(!value[key].is_null()) {
should_append = true;
break;
}
}
}
if(should_append) {
values.emplace_back(std::move(value));
}
}
product_and_values["values"] = values;
api_values.emplace_back(std::move(product_and_values));
}
return api_values;
}
json Compare::get_one_www_value(const json& prod, const std::string& www_request_file_name, const std::map<std::string, std::string>& field_mappings) {
auto dar_query_str = read_query_file_and_replace_consts(prod, www_request_file_name);
LOG(ERROR) << dar_query_str << "\n";
auto dar_query_res_str = Req::make_a_www_query(v2_query_path, dar_query_str);
auto dar_query_res = json::parse(dar_query_res_str);
auto datas = dar_query_res["data"]["facets"];
json product_and_values;
product_and_values["product"] = prod;
json values;
for(auto&& data : datas) {
json value;
for(auto&& key_map_item: field_mappings) {
auto api_key = key_map_item.first;
auto www_key = key_map_item.second;
if(www_key == "date") {
auto timestamp_ = data[www_key].get<uint64_t>();
auto date_str = timestamp_to_utc_time(timestamp_);
value[api_key] = date_str;
}else{
// App Performance 接口Truncate了原先API接口返回的精度
if(data[www_key].is_number_float() && (get_float_fraction(data[www_key].get<float>()) == 0.0)) {
value[api_key] = (uint64_t)(data[www_key].get<float>());
}else {
value[api_key] = data[www_key];
}
}
}
bool should_append = false;
for(auto&& begin=value.begin(), end=value.end(); begin!=end; ++begin) {
auto key = begin.key();
auto key_it = std::find(common_keys.begin(), common_keys.end(), key);
// 没找着这个key
if(key_it == common_keys.end()) {
if(!value[key].is_null()) {
should_append = true;
break;
}
}
}
if(should_append) {
values.emplace_back(std::move(value));
}
}
product_and_values["values"] = values;
return product_and_values;
}
bool Compare::compare_api_and_www_equal(const std::map<std::string, std::string>& field_mappings, const std::string& api_data_field_name, const std::string& result_file_name, const std::string& www_query_file) {
auto api_values = get_api_values(field_mappings, api_data_field_name);
bool is_value_equal = true;
int i = 0;
for(auto&& product_and_values: api_values) {
auto product = product_and_values["product"];
auto values_ = product_and_values["values"];
auto www_values_ = get_one_www_value(
product, www_query_file, field_mappings);
auto act_www_values_ = www_values_["values"];
++i;
// 对比前max_compare_count条,退出
if(i > max_compare_count){
LOG(INFO) << "Hit " << max_compare_count << " records, return\n.";
break;
}
std::sort(values_.begin(), values_.end());
std::sort(act_www_values_.begin(), act_www_values_.end());
auto has_diff = find_diff_and_save_f(values_, act_www_values_, result_file_name);
is_value_equal = !has_diff;
if(!is_value_equal){
LOG(ERROR) << "API and www values are not equals\n";
LOG(ERROR) << "API Length: " << values_.size() << "\n";
LOG(ERROR) << "WWW Length: " << act_www_values_.size() << "\n";
LOG(ERROR) << "Product: \n";
LOG(ERROR) << product << "\n";
LOG(ERROR) << "API " << api_data_field_name << ": \n";
LOG(ERROR) << values_ << "\n";
LOG(ERROR) << "WWW " << api_data_field_name + ": \n";
LOG(ERROR) << act_www_values_ << "\n";
break;
}
}
return is_value_equal;
}
bool Compare::compare_api_and_www_values_equal(const std::map<std::string, std::string>& field_mappings, const std::string& api_data_field_name, const std::vector<std::string>& www_query_file_list) {
auto api_values = get_api_values(field_mappings, api_data_field_name);
bool is_value_equal = true;
int i = 0;
for(auto&& product_and_values: api_values) {
auto product = product_and_values["product"];
auto values_ = product_and_values["values"];
json act_www_values_;
for(auto&& www_query_file : www_query_file_list) {
auto www_values_ = get_one_www_value(
product, www_query_file, field_mappings);
for(auto&& value: www_values_["values"]) {
act_www_values_.emplace_back(std::move(value));
}
}
++i;
// 比前max_compare_count条,退出
// 对比前max_compare_count条,退出
if(i > max_compare_count){
LOG(INFO) << "Hit " << max_compare_count << " records, return\n.";
break;
}
std::sort(values_.begin(), values_.end());
std::sort(act_www_values_.begin(), act_www_values_.end());
auto has_diff = find_diff_and_save_f(values_, act_www_values_, api_data_field_name);
is_value_equal = !has_diff;
if(!is_value_equal){
LOG(ERROR) << "API and www values are not equals\n";
LOG(ERROR) << "API Length: " << values_.size() << "\n";
LOG(ERROR) << "WWW Length: " << act_www_values_.size() << "\n";
LOG(ERROR) << "Product: \n";
LOG(ERROR) << product << "\n";
LOG(ERROR) << "API " << api_data_field_name << ": \n";
LOG(ERROR) << values_ << "\n";
LOG(ERROR) << "WWW " << api_data_field_name + ": \n";
LOG(ERROR) << act_www_values_ << "\n";
break;
}
}
return is_value_equal;
}
json Compare::get_retention_www_values(const json& prod, const std::string& www_request_file_name, const RetentionValue& rent_value) {
auto granu_ = rent_value.granularity;
auto range_list = rent_value.range_list;
auto dar_query_str = read_query_file_and_replace_consts(prod, www_request_file_name);
dar_query_str = pystring::replace(dar_query_str, "month", granu_);
LOG(ERROR) << dar_query_str << "\n";
auto dar_query_res_str = Req::make_a_www_query(v2_query_path, dar_query_str);
auto dar_query_res = json::parse(dar_query_res_str);
auto datas = dar_query_res["data"]["facets"];
json product_and_values;
product_and_values["product"] = prod;
json tmp_list;
json tmp_map;
std::stringstream rent_key_ss {};
rent_key_ss << "retention_" << granu_ << "s";
auto rent_key = rent_key_ss.str();
std::stringstream aggr_key_ss {};
aggr_key_ss << "est_retention_" << granu_ << "__aggr";
auto aggr_key = aggr_key_ss.str();
std::string first_ch = granu_.substr(0, 1);
std::stringstream rent_value_key_ss {};
rent_value_key_ss << "est_retention_" << first_ch;
auto rent_value_key = rent_value_key_ss.str();
for(auto&& data: datas) {
json value;
auto timestamp_ = data["date"].get<uint64_t>();
auto date_str = timestamp_to_utc_time(timestamp_);
value["date"] = date_str;
value["country_code"] = data["country_code"];
value["device_code"] = data["device_code"];
value[rent_key] = data[rent_key];
value[aggr_key] = data[aggr_key];
tmp_list.emplace_back(std::move(value));
}
for(auto&& dic_ele: tmp_list) {
auto key_ = dic_ele["date"].get<std::string>() + "/" +dic_ele["device_code"].get<std::string>() + "/" + dic_ele["country_code"].get<std::string>();
auto key_it = tmp_map.find(key_);
if(key_it == tmp_map.end()) {
tmp_map[key_] = {std::move(dic_ele)};
} else {
tmp_map[key_].emplace_back(std::move(dic_ele));
}
}
for(auto&& begin=tmp_map.begin(), end=tmp_map.end(); begin!=end; ++begin) {
auto date_ = begin.key();
auto eles = begin.value();
json res_value;
std::vector<std::string> results_keys_{};
pystring::split(date_, results_keys_, "/");
res_value["date"] = results_keys_[0];
res_value["device_code"] = results_keys_[1];
res_value["country_code"] = results_keys_[2];
std::vector<int> exist_months{};
for(auto&& ele: eles) {
auto rent_key_it = std::find(range_list.begin(), range_list.end(), ele[rent_key].get<int>());
if(rent_key_it != range_list.end()) {
std::stringstream res_key_ss{};
res_key_ss << rent_value_key << ele[rent_key].get<int>();
auto res_key = res_key_ss.str();
if(ele[aggr_key].is_number_float() && (get_float_fraction(ele[aggr_key].get<float>()) == 0.0)) {
res_value[res_key] = (uint64_t)(ele[aggr_key].get<float>());
}else {
res_value[res_key] = ele[aggr_key];
}
exist_months.emplace_back(ele[rent_key].get<int>());
}
}
std::sort(range_list.begin(), range_list.end());
std::sort(exist_months.begin(), exist_months.end());
std::vector<int> not_exist_months {};
std::set_difference(range_list.begin(), range_list.end(),
exist_months.begin(), exist_months.end(),
std::back_inserter(not_exist_months));
for(auto&& not_exist_month: not_exist_months) {
std::stringstream res_key_ss1{};
res_key_ss1 << rent_value_key << not_exist_month;
auto res_key = res_key_ss1.str();
res_value[res_key] = nullptr;
}
product_and_values["values"].emplace_back(std::move(res_value));
}
return product_and_values;
}
bool Compare::compare_api_and_www_retention_equal(const std::map<std::string, RetentionValue>& field_mappings, const std::string& api_data_field_name, const std::string& result_file_name, const std::string& www_query_file) {
auto granularity = app_perf_result_["granularity"].get<std::string>();
auto rent_value = field_mappings.find(granularity)->second;
auto real_mappings_ = rent_value.mapping;
auto api_values = get_api_values(real_mappings_, api_data_field_name);
bool is_value_equal = true;
int i = 0;
for(auto&& product_and_values: api_values) {
auto product = product_and_values["product"];
auto values_ = product_and_values["values"];
auto www_values_ = get_retention_www_values(
product, www_query_file, rent_value);
auto act_www_values_ = www_values_["values"];
++i;
// 对比前max_compare_count条,退出
if(i > max_compare_count){
LOG(INFO) << "Hit " << max_compare_count << " records, return\n.";
break;
}
std::sort(values_.begin(), values_.end());
std::sort(act_www_values_.begin(), act_www_values_.end());
auto has_diff = find_diff_and_save_f(values_, act_www_values_, result_file_name);
is_value_equal = !has_diff;
if(!is_value_equal){
LOG(ERROR) << "API and www values are not equals\n";
LOG(ERROR) << "API Length: " << values_.size() << "\n";
LOG(ERROR) << "WWW Length: " << act_www_values_.size() << "\n";
LOG(ERROR) << "Product: \n";
LOG(ERROR) << product << "\n";
LOG(ERROR) << "API " << api_data_field_name << ": \n";
LOG(ERROR) << values_ << "\n";
LOG(ERROR) << "WWW " << api_data_field_name + ": \n";
LOG(ERROR) << act_www_values_ << "\n";
break;
}
}
return is_value_equal;
}
utils/case.h
#ifndef _FREDRIC_CASE_H_
#define _FREDRIC_CASE_H_
#include <string>
struct Case {
std::string name;
std::string url;
int expect_code;
};
#endif
utils/beans.h
#ifndef _FREDRIC_BEANS_H_
#define _FREDRIC_BEANS_H_
#include <map>
#include <string>
#include <vector>
struct RetentionValue {
std::string granularity;
std::map<std::string, std::string> mapping;
std::vector<int> range_list;
};
extern std::vector<int> day_ls;
extern std::vector<int> week_ls;
extern std::vector<int> month_ls;
extern std::map<std::string, std::string> DimProductMapping;
extern std::map<std::string, std::string> DownloadAndRevenueMapping;
extern std::map<std::string, std::string> UsageMapping;
extern std::map<std::string, std::string> DemographicsMapping;
extern std::map<std::string, RetentionValue> RetetionMappingObject;
#endif
utils/beans.cpp
#include "api_accuracy/utils/beans.h"
std::vector<int> day_ls {0, 1, 2, 3, 4, 5, 6, 7, 14, 30};
std::vector<int> week_ls {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20};
std::vector<int> month_ls {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
std::map<std::string, std::string> DimProductMapping {
{"product_id", "product_id"},
{"product_name", "name"},
{"product_description", "description"},
{"category_id", "category_id"},
{"unified_product_id", "unified_product_id"},
{"category_name", "category_id/name"},
{"category_slug", "category_id/slug"},
{"market_code", "market_code"},
{"publisher_name", "publisher_id/name"},
{"publisher_market", "publisher_id/market_code"},
{"company_name", "publisher_id/company_id/name"},
{"company_exchange", "publisher_id/company_id/exchange"},
{"company_stock_symbol", "publisher_id/company_id/ticker_symbol"},
{"company_website", "publisher_id/company_id/website"},
{"company_is_public", "publisher_id/company_id/is_public_company"}
};
std::map<std::string, std::string> DownloadAndRevenueMapping {
{"date", "date"},
{"country_code", "country_code"},
{"device_code", "device_code"},
{"est_download", "est_download__sum"},
{"est_revenue", "est_revenue__sum"},
{"est_paid_channel_download", "est_paid_channel_download__sum"},
{"est_organic_channel_download", "est_organic_channel_download__sum"},
{"est_paid_channel_download_share", "est_paid_channel_download_share__aggr"},
{"est_organic_channel_download_share", "est_organic_channel_download_share__aggr"}
};
std::map<std::string, std::string> UsageMapping {
{"date", "date"},
{"country_code", "country_code"},
{"device_code", "device_code"},
{"est_average_active_users", "est_average_active_users__aggr"},
{"est_usage_penetration", "est_usage_penetration__aggr"},
{"est_install_penetration", "est_install_penetration__aggr"},
{"est_install_base", "est_install_base__aggr"},
{"est_open_rate", "est_open_rate__aggr"},
{"est_average_session_per_user", "est_average_session_per_user__aggr"},
{"est_average_session_duration", "est_average_session_duration__aggr"},
{"est_average_time_per_user", "est_average_time_per_user__aggr"},
{"est_total_time", "est_total_time__aggr"},
{"est_average_active_days", "est_average_active_days__aggr"},
{"est_percentage_active_days", "est_percentage_active_days__aggr"},
{"est_share_of_category_time", "est_share_of_category_time__aggr"},
{"est_average_bytes_per_user", "est_average_bytes_per_user__aggr"},
{"est_average_bytes_per_session", "est_average_bytes_per_session__aggr"}
};
std::map<std::string, std::string> DemographicsMapping = {
{"date", "date"},
{"country_code", "country_code"},
{"est_demographics_percent", "est_demographics_percent__aggr"},
{"est_demographics_index", "est_demographics_index__aggr"}
};
std::map<std::string, std::string> RetentionDayMapping = {
{"date", "date"},
{"country_code", "country_code"},
{"device_code", "device_code"},
{"est_retention_d0", "est_retention_d0"},
{"est_retention_d1", "est_retention_d1"},
{"est_retention_d2", "est_retention_d2"},
{"est_retention_d3", "est_retention_d3"},
{"est_retention_d4", "est_retention_d4"},
{"est_retention_d5", "est_retention_d5"},
{"est_retention_d6", "est_retention_d6"},
{"est_retention_d7", "est_retention_d7"},
{"est_retention_d14", "est_retention_d14"},
{"est_retention_d30", "est_retention_d30"}
};
std::map<std::string, std::string> RetentionWeekMapping = {
{"date", "date"},
{"country_code", "country_code"},
{"device_code", "device_code"},
{"est_retention_w0", "est_retention_w0"},
{"est_retention_w1", "est_retention_w1"},
{"est_retention_w2", "est_retention_w2"},
{"est_retention_w3", "est_retention_w3"},
{"est_retention_w4", "est_retention_w4"},
{"est_retention_w5", "est_retention_w5"},
{"est_retention_w6", "est_retention_w6"},
{"est_retention_w7", "est_retention_w7"},
{"est_retention_w8", "est_retention_w8"},
{"est_retention_w9", "est_retention_w9"},
{"est_retention_w10", "est_retention_w10"},
{"est_retention_w20", "est_retention_w20"}
};
std::map<std::string, std::string> RetentionMonthMapping = {
{"date", "date"},
{"country_code", "country_code"},
{"device_code", "device_code"},
{"est_retention_m0", "est_retention_m0"},
{"est_retention_m1", "est_retention_m1"},
{"est_retention_m2", "est_retention_m2"},
{"est_retention_m3", "est_retention_m3"},
{"est_retention_m4", "est_retention_m4"},
{"est_retention_m5", "est_retention_m5"},
{"est_retention_m6", "est_retention_m6"},
{"est_retention_m7", "est_retention_m7"},
{"est_retention_m8", "est_retention_m8"},
{"est_retention_m9", "est_retention_m9"},
{"est_retention_m10", "est_retention_m10"},
{"est_retention_m11", "est_retention_m11"},
{"est_retention_m12", "est_retention_m12"}
};
std::map<std::string, RetentionValue> RetetionMappingObject = {
{"daily", {"day", RetentionDayMapping, day_ls}},
{"weekly",{"week", RetentionWeekMapping, week_ls}},
{"monthly", {"month", RetentionMonthMapping, month_ls}}
};
test/app_perf_test.cpp
#include "api_accuracy/cases/app_perf_cases.h"
#include "api_accuracy/test_cfg.h"
#include "api_accuracy/utils/req.h"
#include "api_accuracy/utils/io_util.h"
#include "api_accuracy/utils/funcs.h"
#include "api_accuracy/utils/beans.h"
#include "api_accuracy/utils/compare.h"
#include "pystring/pystring.h"
#include "json/json.hpp"
#include "death_handler/death_handler.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
using json = nlohmann::json;
int main(int argc, char** argv) {
FLAGS_log_dir = "./";
FLAGS_alsologtostderr = true;
// 日志级别 INFO, WARNING, ERROR, FATAL 的值分别为0、1、2、3
FLAGS_minloglevel = 2;
Debug::DeathHandler dh;
google::InitGoogleLogging("./logs.log");
testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}
class AppPerfTests: public testing::Test {
protected:
virtual void SetUp(){
for(auto&& case_: app_perf_pos_cases) {
auto case_name = case_.name;
auto case_url = case_.url;
if(!use_file) {
auto case_result_ = Req::make_a_api_query(case_url);
bool write_res = IOUtil::write_file(case_name, case_result_);
if(!write_res) {
// Write case result failed
ASSERT_TRUE(false);
}
}
auto content_ = IOUtil::read_file(case_name);
app_perf_results[case_name] = json::parse(content_);
}
}
virtual void TearDown(){
}
std::map<std::string, json> app_perf_results;
};
TEST_F(AppPerfTests, ReadFile) {
auto query_str = IOUtil::read_file(app_perf_pos_cases[0].name);
LOG(INFO) << query_str << "\n";
ASSERT_TRUE(query_str!="");
}
TEST_F(AppPerfTests, GetMapKeys) {
auto keys = get_map_keys(DimProductMapping);
LOG(INFO) << keys.size() << "\n";
ASSERT_EQ(15, keys.size());
}
TEST_F(AppPerfTests, GetMapValues) {
auto values = get_map_values(DimProductMapping);
LOG(INFO) << values.size() << "\n";
ASSERT_EQ(15, values.size());
}
TEST_F(AppPerfTests, GetApiProductFields) {
auto app_perf_result = app_perf_results.begin()->second;
std::cerr << app_perf_result["start_date"] << std::endl;
Compare c {app_perf_result};
auto values = c.get_api_products_fields(DimProductMapping);
ASSERT_TRUE(values.size() > 0);
}
TEST_F(AppPerfTests, JsonDumpStdVector) {
std::string country_code = "US,CN,JP,CA";
std::vector<std::string> results_{};
pystring::split(country_code, results_, ",");
json js = results_;
ASSERT_EQ(4, js.size());
}
TEST_F(AppPerfTests, JsonDelInnerKey) {
auto js_str = R"(
{
"person": {
"name": "Zhangsan",
"age": 34
},
"grade": 3
}
)";
json js = json::parse(js_str);
js["person"].erase("age");
LOG(INFO) << js.dump() << "\n";
}
TEST_F(AppPerfTests, ReadQueryFileAndReplaceConsts) {
json prod = json::parse(R"({
"product_id": 1111,
"market_code": "google-play",
"start_date": "2021-01-01",
"end_date": "2021-01-31",
"countries": "CN,US,JP",
"devices": "android-all,android-tv",
"granularity": "monthly"
})");
auto apps_meta_query_str = read_query_file_and_replace_consts(prod, apps_meta_file);
LOG(INFO) << apps_meta_query_str << "\n";
}
TEST_F(AppPerfTests, ReadQueryFileAndReplaceConstsAllCountries) {
json prod = json::parse(R"({
"product_id": 1111,
"market_code": "google-play",
"start_date": "2021-01-01",
"end_date": "2021-01-31",
"countries": "all_supported",
"devices": "android-all,android-tv",
"granularity": "monthly"
})");
auto apps_meta_query_str = read_query_file_and_replace_consts(prod, apps_meta_file);
LOG(INFO) << apps_meta_query_str << "\n";
}
TEST_F(AppPerfTests, ParseDimProductFields) {
auto dim_file_str = IOUtil::read_file(test_dim_parse_data_file);
auto dim_file = json::parse(dim_file_str);
auto dim_keys_ = get_map_values(DimProductMapping);
auto dim_values = parse_dim_product_fields(dim_file, dim_keys_);
auto api_values = convert_dim_values_to_api_values(dim_values, DimProductMapping);
LOG(INFO) << api_values << "\n";
}
TEST_F(AppPerfTests, TestProductMetadata) {
bool is_case_pass = true;
for(auto&& case_item: app_perf_results) {
auto app_perf_file = case_item.first;
auto app_perf_result = case_item.second;
auto comp_ = Compare(app_perf_result);
auto is_value_equal = comp_.compare_api_and_www_meta_equal(DimProductMapping, apps_meta_file);
if(!is_value_equal) {
LOG(ERROR) << "[" << app_perf_file << "] test [test_product_metadata] failed!\n";
is_case_pass = false;
}
}
ASSERT_TRUE(is_case_pass);
}
TEST_F(AppPerfTests, GetApiValues) {
auto app_perf_result = app_perf_results.begin()->second;
auto comp_ = Compare(app_perf_result);
auto api_values = comp_.get_api_values(DownloadAndRevenueMapping, "download_revenue");
ASSERT_TRUE(api_values.size() > 0);
}
TEST_F(AppPerfTests, TestDownloadAndRevenue) {
bool is_case_pass = true;
for(auto&& case_item: app_perf_results) {
auto app_perf_file = case_item.first;
auto app_perf_result = case_item.second;
auto comp_ = Compare(app_perf_result);
auto is_value_equal = comp_.compare_api_and_www_equal(DownloadAndRevenueMapping, "app_performance", "download_revenue", download_and_revenue);
if(!is_value_equal) {
LOG(ERROR) << "[" << app_perf_file << "] test [test_download_and_revenue] failed!\n";
is_case_pass = false;
}
}
ASSERT_TRUE(is_case_pass);
}
TEST_F(AppPerfTests, TestUsage) {
bool is_case_pass = true;
for(auto&& case_item: app_perf_results) {
auto app_perf_file = case_item.first;
auto app_perf_result = case_item.second;
auto comp_ = Compare(app_perf_result);
auto is_value_equal = comp_.compare_api_and_www_equal(UsageMapping, "app_performance", "usage", usage_file);
if(!is_value_equal) {
LOG(ERROR) << "[" << app_perf_file << "] test [test_usage] failed!\n";
is_case_pass = false;
}
}
ASSERT_TRUE(is_case_pass);
}
TEST_F(AppPerfTests, TestDemographics) {
bool is_case_pass = true;
for(auto&& case_item: app_perf_results) {
auto app_perf_file = case_item.first;
auto app_perf_result = case_item.second;
auto comp_ = Compare(app_perf_result);
auto is_value_equal = comp_.compare_api_and_www_values_equal(
DemographicsMapping, "demographics", {demographics_age_file, demographics_gender_file});
if(!is_value_equal) {
LOG(ERROR) << "[" << app_perf_file << "] test [test_usage] failed!\n";
is_case_pass = false;
}
}
ASSERT_TRUE(is_case_pass);
}
TEST_F(AppPerfTests, TestRentention) {
bool is_case_pass = true;
for(auto&& case_item: app_perf_results) {
auto app_perf_file = case_item.first;
auto app_perf_result = case_item.second;
auto comp_ = Compare(app_perf_result);
auto is_value_equal = comp_.compare_api_and_www_retention_equal(
RetetionMappingObject, "app_performance", "retention", retention_file);
if(!is_value_equal) {
LOG(ERROR) << "[" << app_perf_file << "] test [test_usage] failed!\n";
is_case_pass = false;
}
}
ASSERT_TRUE(is_case_pass);
}
TEST_F(AppPerfTests, TimestampToUtcTime) {
auto time_ = timestamp_to_utc_time(1630800000000);
ASSERT_EQ("2021-09-05", time_);
auto time_stamp = utc_timestamp_from_string("2021-09-05");
ASSERT_EQ(1630800000, time_stamp);
}
test/app_perf_neg_test.cpp
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "api_accuracy/cases/app_perf_cases.h"
#include "api_accuracy/utils/req.h"
#include "death_handler/death_handler.h"
#include "json/json.hpp"
using json = nlohmann::json;
int main(int argc, char** argv) {
FLAGS_log_dir = "./";
FLAGS_alsologtostderr = true;
// 日志级别 INFO, WARNING, ERROR, FATAL 的值分别为0、1、2、3
FLAGS_minloglevel = 2;
Debug::DeathHandler dh;
google::InitGoogleLogging("./logs.log");
testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}
class AppPerfNegTests : public testing::Test {
protected:
virtual void SetUp() { negative_cases = app_perf_neg_cases; }
virtual void TearDown() {}
std::vector<Case> negative_cases;
};
TEST_F(AppPerfNegTests, TestNegativeCases) {
bool is_case_pass = true;
for (auto&& negative_case : negative_cases) {
auto name = negative_case.name;
auto url = negative_case.url;
auto expect_code = negative_case.expect_code;
auto query_result_str = Req::make_a_api_query(url);
auto res = json::parse(query_result_str);
auto code_it = res.find("code");
if (code_it != res.end()) {
// {
// "code": 400,
// "error": "Input error: PLEASE SPECIFY ONE OF THE
// PARAMETERS: [company_id, apps]."
// }
if (res["code"] != expect_code) {
LOG(ERROR) << "[" << url << "] test [" << name << "] failed!\n";
is_case_pass = false;
}
} else {
// {
// "errors": [
// {
// "code": "400",
// "message": "AAFlaskException: Missing query
// parameter 'start_date'"
// }
// ]
// }
auto code_str = res["errors"][0]["code"].get<std::string>();
auto code_ = std::atoi(code_str.c_str());
if (code_ != expect_code) {
LOG(ERROR) << "[" << url << "] test [" << name << "] failed!\n";
is_case_pass = false;
}
}
}
ASSERT_TRUE(is_case_pass);
}
cases/app_perf_cases.h
#ifndef _FREDRIC_APP_PERF_CASES_H_
#define _FREDRIC_APP_PERF_CASES_H_
#include "api_accuracy/utils/case.h"
#include <vector>
extern std::vector<Case> app_perf_pos_cases;
extern std::vector<Case> app_perf_neg_cases;
#endif
cases/app_perf_cases.cpp
#include "api_accuracy/cases/app_perf_cases.h"
std::vector<Case> app_perf_pos_cases {
{"../datas/app_perf/test545__200_ok_monthly_api_response", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=monthly&start_date=2020-01-01&end_date=2020-03-31&countries=US,CN,JP,GB", 0},
{"../datas/app_perf/test546__200_ok_daily_api_response", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=daily&start_date=2020-01-01&end_date=2020-01-10&countries=US,CN", 0},
{"../datas/app_perf/test547__200_ok_weekly_api_response", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=weekly&start_date=2020-01-01&end_date=2020-01-31&countries=US,CN", 0},
{"../datas/app_perf/test548__200_ok_by_apps_api_response", "/v1.3/portfolio/app-performance/less?apps=284882215,20600000009072,30600000702686&granularity=monthly&start_date=2020-01-01&end_date=2020-03-31&countries=US", 0},
{"../datas/app_perf/test549__200_ok_all_countries_api_response", "/v1.3/portfolio/app-performance/less?apps=284882215&granularity=monthly&start_date=2020-01-01&end_date=2020-01-31", 0},
{"../datas/app_perf/test550__200_ok_filter_device_api_response", "/v1.3/portfolio/app-performance/less?apps=284882215,20600000009072&granularity=monthly&start_date=2020-01-01&end_date=2020-01-31&countries=US&device=ios-phone", 0},
{"../datas/app_perf/test556__200_ok_missing_granularity_api_response", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&start_date=2020-01-01&end_date=2020-01-01&countries=US", 0},
{"../datas/app_perf/test561__200_param_all_metric_api_response", "/v1.3/portfolio/app-performance/less?apps=284882215&granularity=daily&start_date=2020-01-01&end_date=2021-01-01&countries=US&metric=all", 0},
{"../datas/app_perf/test562__200_param_all_device_api_response", "/v1.3/portfolio/app-performance/less?apps=284882215,20600000009072&granularity=daily&start_date=2020-01-01&end_date=2021-01-01&countries=US&device=all", 0}
};
std::vector<Case> app_perf_neg_cases {
{"test551__400_err_invalid_country", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=monthly&start_date=2020-01-01&end_date=2020-01-31&countries=YY", 400},
{"test552__400_err_invalid_device_api_response", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=monthly&start_date=2020-01-01&end_date=2020-01-31&device=vivo-phone", 400},
{"test553__400_err_invalid_granularity", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=anually&start_date=2020-01-01&end_date=2020-01-31", 400},
{"test554__400_err_invalid_entry", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&apps=284882215&granularity=monthly&start_date=2020-01-01&end_date=2020-01-31", 400},
{"test555__400_err_missing_entry", "/v1.3/portfolio/app-performance/less?granularity=monthly&start_date=2020-01-01&end_date=2020-01-31", 400},
{"test557__400_err_missing_start_date", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=daily&end_date=2020-01-31", 400},
{"test558__400_err_missing_end_date", "/v1.3/portfolio/app-performance/less?company_id=1000200000000034&granularity=daily&start_date=2020-01-01", 400},
{"test559__400_invalid_company_id", "/v1.3/portfolio/app-performance/less?company_id=facebook&granularity=daily&start_date=2020-01-01&end_date=2021-01-01", 400},
{"test559__400_invalid_product_id", "/v1.3/portfolio/app-performance/less?apps=instagram&granularity=daily&start_date=2020-01-01&end_date=2021-01-01", 400},
{"test563__400_invalid_metric", "/v1.3/portfolio/app-performance/less?apps=284882215&granularity=daily&start_date=2020-01-01&end_date=2021-01-01&metric=usage,non_existed", 400}
};
include/df/df.h
#ifndef _FREDRIC_DF_H_
#define _FREDRIC_DF_H_
#include "json/json.hpp"
#include <DataFrame/DataFrame.h>
#include <DataFrame/DataFrameFinancialVisitors.h>
#include <DataFrame/DataFrameMLVisitors.h>
#include <DataFrame/DataFrameOperators.h>
#include <DataFrame/DataFrameStatsVisitors.h>
#include <vector>
using json = nlohmann::json;
using CDataFrame = hmdf::StdDataFrame<unsigned int>;
using concat_policy = hmdf::concat_policy;
using join_policy = hmdf::join_policy;
const std::string Keys = "keys_";
struct df_op {
static CDataFrame convert_json_to_df(const json& js, const std::vector<std::string>& pri_keys_);
static std::vector<std::string> get_df_keys(const json& js);
static CDataFrame remove_duplicate(const CDataFrame& df, const std::vector<std::string>& keys_);
static bool write_to_csv(const CDataFrame& df, const std::string& csv_file_name);
};
#endif
include/df/df.cpp
#include "df/df.h"
#include <glog/logging.h>
CDataFrame df_op::convert_json_to_df(const json& js, const std::vector<std::string>& pri_keys_) {
CDataFrame::set_thread_level(10);
CDataFrame df;
unsigned long idx = 1ul;
std::vector<unsigned long> ulidxs{};
std::vector<std::string> keys_ = get_df_keys(js);
if(keys_.size() == 0) {
return df;
}
std::map<std::string, std::vector<json>> columns {};
for (auto&& ele_js : js) {
std::string key {};
for(auto column_key: keys_) {
if(columns.find(column_key) == columns.end()) {
std::vector<json> tmp_v {ele_js[column_key]};
columns[column_key] = std::move(tmp_v);
} else {
columns[column_key].emplace_back(std::move(ele_js[column_key]));
}
// No primary keys specified, all columns are considered as primary keys
if(pri_keys_.size() == 0) {
key += ele_js[column_key].dump();
} else {
auto key_it_ = std::find(pri_keys_.begin(), pri_keys_.end(), column_key);
if(key_it_ != pri_keys_.end()) {
key += ele_js[column_key].dump();
}
}
}
if(columns.find(Keys) == columns.end()) {
std::vector<json> tmp_v {json(key)};
columns[Keys] = std::move(tmp_v);
} else {
columns[Keys].emplace_back(std::move(json(key)));
}
ulidxs.emplace_back(idx++);
}
df.load_index(ulidxs.begin(), ulidxs.end());
for(auto&& key: keys_) {
df.load_column<json>(key.c_str(), {columns[key].begin(), columns[key].end()}, hmdf::nan_policy::pad_with_nans);
}
df.load_column<json>(Keys.c_str(), {columns[Keys].begin(), columns[Keys].end()}, hmdf::nan_policy::pad_with_nans);
return df;
}
std::vector<std::string> df_op::get_df_keys(const json& js) {
std::vector<std::string> keys_{};
if(js.size() == 0) {
LOG(ERROR) << "Json list size is zero, empty list!!" << "\n";
return keys_;
}
auto ele_0 = js[0];
for (auto &&begin = ele_0.begin(), end = ele_0.end(); begin != end; ++begin) {
auto key = begin.key();
keys_.emplace_back(key);
}
return keys_;
}
CDataFrame df_op::remove_duplicate(const CDataFrame& df, const std::vector<std::string>& keys_) {
auto size_ = keys_.size();
if(size_ == 1) {
return df.remove_duplicates<json>(keys_[0].c_str(), false, hmdf::remove_dup_spec::keep_none);
} else if(size_ == 2) {
return df.remove_duplicates<json, json>(keys_[0].c_str(), keys_[1].c_str(), false, hmdf::remove_dup_spec::keep_none);
} else if(size_ == 3) {
return df.remove_duplicates<json, json, json>(keys_[0].c_str(), keys_[1].c_str(),keys_[2].c_str(), false, hmdf::remove_dup_spec::keep_none);
} else if(size_ == 4) {
return df.remove_duplicates<json, json, json, json>(keys_[0].c_str(), keys_[1].c_str(),keys_[2].c_str(), keys_[3].c_str(), false, hmdf::remove_dup_spec::keep_none);
} else if(size_ == 5) {
return df.remove_duplicates<json, json, json, json, json>(keys_[0].c_str(), keys_[1].c_str(),keys_[2].c_str(), keys_[3].c_str(), keys_[4].c_str(), false, hmdf::remove_dup_spec::keep_none);
} else if(size_ == 6) {
return df.remove_duplicates<json, json, json, json, json, json>(keys_[0].c_str(), keys_[1].c_str(),keys_[2].c_str(), keys_[3].c_str(), keys_[4].c_str(), keys_[5].c_str() , false, hmdf::remove_dup_spec::keep_none);
} else {
throw std::runtime_error("Not supported argument length, greater than 6!");
}
}
bool df_op::write_to_csv(const CDataFrame& df, const std::string& csv_file_name) {
std::fstream fs {csv_file_name, std::ios::out | std::ios::trunc};
if(!fs.is_open()) {
LOG(ERROR) << "Open file failed" << "\n";
return false;
}
df.write<std::ostream, json>(fs, hmdf::io_format::csv2, true);
fs.close();
return true;
}
test/datas/app_perf/apps_meta_data.json
{
"facets": ["product_id"],
"filters": {
"product_id": {
"equal": ${product_id}
},
"granularity": {
"equal": "${granularity}"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
},
"country_code": {
"in": ${country_code}
}
},
"fields": {
"product_id": {
"fields": [
"name",
"market_code",
"device_code",
"description",
"price",
"has_iap",
"unified_product_id",
"category_id",
"publisher_id"
],
"publisher_id": {
"fields": [
"name",
"market_code",
"website",
"company_id"
],
"company_id": {
"fields": [
"name",
"exchange",
"ticker_symbol",
"website",
"is_public_company",
"country_code"
]
}
},
"category_id": {"fields": ["name", "slug"]}
}
}
}
test/datas/app_perf/demographics_age.json
{
"facets": [
"est_demographics_percent__aggr",
"est_demographics_index__aggr"
],
"filters": {
"product_id": {
"equal": ${product_id}
},
"vertical_code": {
"equal": "app"
},
"granularity": {
"equal": "${granularity}"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
},
"country_code": {
"in": ${country_code}
},
"gender_code": {"equal": "all"},
"age_code": {"in": ["lt25", "gte25_lte44", "gt44"]}
},
"breakdowns": {
"product_id": {},
"device_code": {},
"country_code": {},
"date": {},
"age_code": {}
}
}
test/datas/app_perf/demographics_gender.json
{
"facets": [
"est_demographics_percent__aggr",
"est_demographics_index__aggr"
],
"filters": {
"product_id": {
"equal": ${product_id}
},
"granularity": {
"equal": "${granularity}"
},
"vertical_code": {
"equal": "app"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
},
"country_code": {
"in": ${country_code}
},
"gender_code": {"in": ["male", "female"]},
"age_code": {"equal": "all"}
},
"breakdowns": {
"product_id": {},
"device_code": {},
"country_code": {},
"date": {},
"gender_code": {}
}
}
test/datas/app_perf/download_and_revenue.json
{
"facets": [
"est_download__sum",
"est_cumulative_download__aggr",
"est_revenue__sum",
"est_cumulative_revenue__aggr",
"est_paid_channel_download__sum",
"est_organic_channel_download__sum",
"est_paid_channel_download_share__aggr",
"est_organic_channel_download_share__aggr",
"est_rpd__aggr"
],
"filters": {
"product_id": {
"equal": ${product_id}
},
"country_code": {
"in": ${country_code}
},
"market_code": {
"equal": "${market_code}"
},
"granularity": {
"equal": "${granularity}"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
}
},
"breakdowns": {
"product_id": {},
"device_code": {},
"country_code": {},
"date": {}
}
}
test/datas/app_perf/retention.json
{
"facets": [
"est_retention_month__aggr"
],
"filters": {
"product_id": {
"equal": ${product_id}
},
"country_code": {
"in": ${country_code}
},
"market_code": {
"equal": "${market_code}"
},
"granularity": {
"equal": "${granularity}"
},
"vertical_code": {
"equal": "app"
},
"vertical_code": {
"equal": "app"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
}
},
"breakdowns": {
"product_id": {},
"device_code": {},
"country_code": {},
"date": {},
"retention_months": {}
}
}
test/datas/app_perf/test_dim_parse_data.json
{
"data":{
"facets":[
{
"name":"Meteor Idle",
"market_code":"apple-store",
"device_code":"ios-all",
"description":"Make perfect shots! Collect all stars! Unlock upgrades to get highest score!",
"price":0,
"has_iap":false,
"unified_product_id":1000600000711782,
"unified_category_id":800001,
"category_id":100001,
"publisher_id":1278916211,
"product_id":1299893104,
"facet":"product_id"
}
],
"dimensions":{
"product_id":{
"1299893104":{
"name":"Meteor Idle",
"market_code":"apple-store",
"device_code":"ios-all",
"description":"Make perfect shots! Collect all stars! Unlock upgrades to get highest score!",
"price":0,
"has_iap":false,
"unified_product_id":1000600000711782,
"unified_category_id":800001,
"category_id":100001,
"publisher_id":1278916211,
"product_id":1299893104,
"facet":"product_id"
}
},
"category_id":{
"100001":{
"name":"Games",
"slug":"games",
"category_id":100001,
"facet":"category_id"
}
},
"publisher_id":{
"1278916211":{
"name":"Andrei Sokal",
"market_code":"apple-store",
"website":null,
"company_id":1000200000265389,
"publisher_id":1278916211,
"facet":"publisher_id"
}
},
"company_id":{
"1000200000265389":{
"name":"SayGames",
"exchange":null,
"ticker_symbol":null,
"website":"https://saygames.by/",
"is_public_company":false,
"country_code":"BY",
"company_id":1000200000265389,
"facet":"company_id"
}
}
}
}
}
test/datas/app_perf/usage.json
{
"facets": [
"est_average_active_users__aggr",
"est_usage_penetration__aggr",
"est_install_penetration__aggr",
"est_install_base__aggr",
"est_open_rate__aggr",
"est_average_session_per_user__aggr",
"est_average_session_duration__aggr",
"est_average_time_per_user__aggr",
"est_total_time__aggr",
"est_average_active_days__aggr",
"est_percentage_active_days__aggr",
"est_share_of_category_time__aggr",
"est_average_bytes_per_user__aggr",
"est_average_bytes_per_session__aggr"
],
"filters": {
"product_id": {
"equal": ${product_id}
},
"country_code": {
"in": ${country_code}
},
"market_code": {
"equal": "${market_code}"
},
"granularity": {
"equal": "${granularity}"
},
"date": {
"between": [
"${start_date}",
"${end_date}"
]
}
},
"breakdowns": {
"product_id": {},
"device_code": {},
"country_code": {},
"date": {}
}
}
程序输出如下,