使用C++11做json比对测试,终版

本版本优化点,

  1. 添加更多测试用例。
  2. 优化CMakeLists.txt文件编译流程,先生成一个动态链接库,再让源文件都链接到动态库,这样就只需要编译一次依赖库了。节省一半的编译时间。
  3. 优化CMakeLists.txt文件,测试文件分离,一个可执行文件只绑定到一个测试用例的cpp文件。
  4. 添加时间戳到时间的转换方法,以及时间到时间戳的转换方法。

程序目录结构如下,


image.png

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": {}
    }
  }

程序输出如下,


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容