Add Privacy Mode and Serivce Certificate Support
This merges the following changes from the Widevine CDM repository:
da001b6 Add Privacy mode and service certificate
This adds support to the CDM for privacy mode and service certificates.
92bf200 Add support for using Youtube Content Protection server for testing
Enables testing with Youtube Content Protection server. Google Play license
server is still the default. Select YTCP server by using the flag -icp
e.g. adb shell '/system/bin/request_license_test -icp'
85dcd60 Fixes to enable privacy mode
These includes changes to use PKCS7 padding, corrected root CA formatting
and changes to integration test. Also refactored service certificate
handling.
989971c Correction to request license test
Corrected PropertySetTest to provision when needed. Also added disabled
privacy tests to run against YTCP staging server until GooglePlay
integration is complete.
Bug: 10109249
Change-Id: If81d68c65d743d77a485406f48d1be41a74de0af
This commit is contained in:
@@ -23,15 +23,28 @@ namespace {
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
wvcdm::ConfigTestEnv* g_config = NULL;
|
||||
wvcdm::KeyId g_key_id;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
std::string g_license_server;
|
||||
std::string g_port;
|
||||
wvcdm::KeyId g_wrong_key_id;
|
||||
int g_use_full_path = 0; // cannot use boolean in getopt_long
|
||||
} // namespace
|
||||
bool g_use_chunked_transfer = false;
|
||||
bool g_use_full_path = false;
|
||||
bool g_use_secure_transfer = false;
|
||||
wvcdm::LicenseServerId g_license_server_id = wvcdm::kGooglePlayServer;
|
||||
|
||||
namespace wvcdm {
|
||||
std::string kServiceCertificate =
|
||||
"0803120F736572766963655F636572745F736E187B228E023082010A028201010"
|
||||
"0A700366065DCBD545A2A40B4E1159458114F9458DDDEA71F3C2CE08809296157"
|
||||
"675E567EEE278F59349A2AAA9DB44EFAA76AD4C97A53C14E9FE334F73DB7C9104"
|
||||
"74F28DA3FCE317BFD0610EBF7BE92F9AFFB3E68DAEE1A644CF329F2739E39D8F6"
|
||||
"6FD8B28082718EB5A4F2C23ECD0ACAB604CD9A138B54735425548CBE987A67ADD"
|
||||
"AB34EB3FA82A84A679856575471CD127FEDA301C06A8B24039688BE97662ABC53"
|
||||
"C98306515A88651318E43AED6BF1615B4CC81EF4C2AE085E2D5FF8127FA2FCBB2"
|
||||
"11830DAFE40FB01CA2E370ECEDD768782460B3A778FC072072C7F9D1E865BED27"
|
||||
"29DF039762EF44D35B3DDB9C5E1B7B39B40B6D046BBBBB2C5FCFB37A050203010"
|
||||
"0013A0D6D79736572766963652E636F6D";
|
||||
|
||||
// TODO(rfrias): refactor to print out the decryption test names
|
||||
struct SubSampleInfo {
|
||||
@@ -192,9 +205,7 @@ SubSampleInfo partial_offset_single_encrypted_sub_sample = {
|
||||
namespace wvcdm {
|
||||
class TestWvCdmClientPropertySet : public CdmClientPropertySet {
|
||||
public:
|
||||
TestWvCdmClientPropertySet()
|
||||
: service_certificate_(std::vector<uint8_t>()),
|
||||
use_privacy_mode_(false) {}
|
||||
TestWvCdmClientPropertySet() : use_privacy_mode_(false) {}
|
||||
virtual ~TestWvCdmClientPropertySet() {}
|
||||
|
||||
virtual std::string security_level() const { return security_level_; }
|
||||
@@ -289,32 +300,27 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
const std::string& client_auth,
|
||||
int expected_response) {
|
||||
// Use secure connection and chunk transfer coding.
|
||||
UrlRequest url_request(server_url + client_auth, g_port, true, true);
|
||||
UrlRequest url_request(server_url + client_auth, g_port,
|
||||
g_use_secure_transfer, g_use_chunked_transfer);
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string message;
|
||||
int resp_bytes = url_request.GetResponse(&message);
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
LOGD("end %s ", message.c_str());
|
||||
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
int status_code = url_request.GetStatusCode(message);
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, status_code);
|
||||
} else {
|
||||
EXPECT_NE(200, status_code);
|
||||
}
|
||||
|
||||
std::string drm_msg;
|
||||
if (200 == status_code) {
|
||||
LicenseRequest lic_request;
|
||||
lic_request.GetDrmMessage(message, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
LOGV("HTTP response body: (%u bytes)", drm_msg.size());
|
||||
}
|
||||
return drm_msg;
|
||||
}
|
||||
@@ -324,7 +330,7 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
std::string GetCertRequestResponse(const std::string& server_url,
|
||||
int expected_response) {
|
||||
// Use secure connection and chunk transfer coding.
|
||||
UrlRequest url_request(server_url, g_port, true, true);
|
||||
UrlRequest url_request(server_url, kDefaultHttpsPort, true, true);
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
@@ -361,7 +367,6 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
wvcdm::ConfigTestEnv config_;
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
CdmKeyMessage key_msg_;
|
||||
CdmSessionId session_id_;
|
||||
@@ -378,10 +383,10 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) {
|
||||
|
||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest(
|
||||
&key_msg_, &provisioning_server_url));
|
||||
EXPECT_EQ(provisioning_server_url, config_.provisioning_server_url());
|
||||
EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url());
|
||||
|
||||
std::string response =
|
||||
GetCertRequestResponse(config_.provisioning_test_server_url(), 200);
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse(response));
|
||||
decryptor_.CloseSession(session_id_);
|
||||
@@ -393,19 +398,19 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningRetryTest) {
|
||||
|
||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest(
|
||||
&key_msg_, &provisioning_server_url));
|
||||
EXPECT_EQ(provisioning_server_url, config_.provisioning_server_url());
|
||||
EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url());
|
||||
|
||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest(
|
||||
&key_msg_, &provisioning_server_url));
|
||||
EXPECT_EQ(provisioning_server_url, config_.provisioning_server_url());
|
||||
EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url());
|
||||
|
||||
std::string response =
|
||||
GetCertRequestResponse(config_.provisioning_test_server_url(), 200);
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse(response));
|
||||
|
||||
response =
|
||||
GetCertRequestResponse(config_.provisioning_test_server_url(), 200);
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(wvcdm::UNKNOWN_ERROR,
|
||||
decryptor_.HandleProvisioningResponse(response));
|
||||
@@ -425,7 +430,26 @@ TEST_F(WvCdmRequestLicenseTest, PropertySetTest) {
|
||||
decryptor_.OpenSession(g_key_system, &property_set_L1, &session_id_L1);
|
||||
property_set_L3.set_security_level(QUERY_VALUE_SECURITY_LEVEL_L3);
|
||||
property_set_L3.set_use_privacy_mode(false);
|
||||
decryptor_.OpenSession(g_key_system, &property_set_L3, &session_id_L3);
|
||||
|
||||
CdmResponseType sts = decryptor_.OpenSession(g_key_system, &property_set_L3,
|
||||
&session_id_L3);
|
||||
|
||||
if (NEED_PROVISIONING == sts) {
|
||||
std::string provisioning_server_url;
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.GetProvisioningRequest(&key_msg_, &provisioning_server_url));
|
||||
EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url());
|
||||
std::string response =
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(NO_ERROR, decryptor_.HandleProvisioningResponse(response));
|
||||
EXPECT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set_L3,
|
||||
&session_id_L3));
|
||||
} else {
|
||||
EXPECT_EQ(NO_ERROR, sts);
|
||||
}
|
||||
|
||||
property_set_Ln.set_security_level("");
|
||||
decryptor_.OpenSession(g_key_system, &property_set_Ln, &session_id_Ln);
|
||||
|
||||
@@ -459,9 +483,9 @@ TEST_F(WvCdmRequestLicenseTest, ForceL3Test) {
|
||||
EXPECT_EQ(NO_ERROR,
|
||||
decryptor_.GetProvisioningRequest(&key_msg_,
|
||||
&provisioning_server_url));
|
||||
EXPECT_EQ(provisioning_server_url, config_.provisioning_server_url());
|
||||
EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url());
|
||||
std::string response =
|
||||
GetCertRequestResponse(config_.provisioning_test_server_url(), 200);
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(NO_ERROR, decryptor_.HandleProvisioningResponse(response));
|
||||
|
||||
@@ -472,33 +496,27 @@ TEST_F(WvCdmRequestLicenseTest, ForceL3Test) {
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, UsePrivacyModeTest) {
|
||||
TEST_F(WvCdmRequestLicenseTest, DISABLED_PrivacyModeTest) {
|
||||
TestWvCdmClientPropertySet property_set;
|
||||
|
||||
property_set.set_use_privacy_mode(true);
|
||||
decryptor_.OpenSession(g_key_system, &property_set, &session_id_);
|
||||
|
||||
if (property_set.service_certificate().empty()) {
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
std::string resp = GetKeyRequestResponse(g_license_server,
|
||||
g_client_auth, 200);
|
||||
EXPECT_EQ(decryptor_.AddKey(session_id_, resp, &key_set_id_),
|
||||
wvcdm::NEED_KEY);
|
||||
std::vector<uint8_t> service_certificate(key_msg_.begin(), key_msg_.end());
|
||||
property_set.set_service_certificate(service_certificate);
|
||||
}
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
std::string resp = GetKeyRequestResponse(g_license_server,
|
||||
g_client_auth, 200);
|
||||
EXPECT_EQ(decryptor_.AddKey(session_id_, resp, &key_set_id_),
|
||||
wvcdm::NEED_KEY);
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
// property has service certificate set from previous request
|
||||
EXPECT_FALSE(property_set.service_certificate().empty());
|
||||
decryptor_.OpenSession(g_key_system, &property_set, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
TEST_F(WvCdmRequestLicenseTest, DISABLED_PrivacyModeWithServiceCertificateTest) {
|
||||
TestWvCdmClientPropertySet property_set;
|
||||
|
||||
property_set.set_use_privacy_mode(false);
|
||||
property_set.set_use_privacy_mode(true);
|
||||
property_set.set_service_certificate(a2b_hex(kServiceCertificate));
|
||||
decryptor_.OpenSession(g_key_system, &property_set, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
@@ -521,7 +539,7 @@ TEST_F(WvCdmRequestLicenseTest, WrongMessageTest) {
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, AddSteamingKeyTest) {
|
||||
TEST_F(WvCdmRequestLicenseTest, AddStreamingKeyTest) {
|
||||
decryptor_.OpenSession(g_key_system, NULL, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
@@ -913,32 +931,92 @@ TEST_F(WvCdmRequestLicenseTest, KeyControlBlockDecryptionTest) {
|
||||
*/
|
||||
} // namespace wvcdm
|
||||
|
||||
void show_menu(char* prog_name) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << prog_name << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell"
|
||||
<< std::endl;
|
||||
std::cout << " e.g. adb shell '" << prog_name << " --server=\"url\"'"
|
||||
<< std::endl;
|
||||
std::cout << " or adb shell '" << prog_name << " -u\"url\"'"
|
||||
<< std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left << " -c/--chunked_transfer";
|
||||
std::cout << "specifies chunked transfer encoding in request"
|
||||
<< std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left << " -f/--use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left
|
||||
<< " -i/--license_server_id=<gp/cp>";
|
||||
std::cout << "specifies which default server settings to use: " << std::endl;
|
||||
std::cout << std::setw(35) << std::left << " ";
|
||||
std::cout << "gp (case sensitive) for GooglePlay server" << std::endl;
|
||||
std::cout << std::setw(35) << std::left << " ";
|
||||
std::cout << "cp (case sensitive) for Youtube Content Protection server"
|
||||
<< std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left << " -k/--keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format"
|
||||
<< std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left
|
||||
<< " -p/--port=<port>";
|
||||
std::cout << "specifies the connection port" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left
|
||||
<< " -s/--secure_transfer";
|
||||
std::cout << "use https transfer protocol" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(35) << std::left
|
||||
<< " -u/--server=<server_url>";
|
||||
std::cout
|
||||
<< "configure the license server url, please include http[s] in the url"
|
||||
<< std::endl << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
wvcdm::ConfigTestEnv config;
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_wrong_key_id.assign(config.wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
bool show_usage = false;
|
||||
static const struct option long_options[] = {
|
||||
{"use_full_path", no_argument, &g_use_full_path, 0},
|
||||
{"keyid", required_argument, NULL, 'k'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"server", required_argument, NULL, 's'}, {NULL, 0, NULL, '\0'}};
|
||||
{ "chunked_transfer", no_argument, NULL, 'c' },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "license_server_id", required_argument, NULL, 'i' },
|
||||
{ "license_server_url", required_argument, NULL, 'u' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "secure_transfer", no_argument, NULL, 's' },
|
||||
{ "use_full_path", no_argument, NULL, 'f' },
|
||||
{ NULL, 0, NULL, '\0' }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
int opt = 0;
|
||||
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options,
|
||||
while ((opt = getopt_long(argc, argv, "cfi:k:p:su:", long_options,
|
||||
&option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'c': {
|
||||
g_use_chunked_transfer = true;
|
||||
break;
|
||||
}
|
||||
case 'f': {
|
||||
g_use_full_path = true;
|
||||
break;
|
||||
}
|
||||
case 'i': {
|
||||
std::string license_id(optarg);
|
||||
if (!license_id.compare("gp")) {
|
||||
g_license_server_id = wvcdm::kGooglePlayServer;
|
||||
} else if (!license_id.compare("cp")) {
|
||||
g_license_server_id = wvcdm::kYouTubeContentProtectionServer;
|
||||
} else {
|
||||
std::cout << "Invalid license server id" << optarg << std::endl;
|
||||
show_usage = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'k': {
|
||||
g_key_id.clear();
|
||||
g_key_id.assign(optarg);
|
||||
@@ -950,61 +1028,62 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_use_secure_transfer = true;
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_license_server.clear();
|
||||
g_license_server.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_use_full_path = 1;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
show_usage = 1;
|
||||
show_usage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_usage) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell"
|
||||
<< std::endl;
|
||||
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'"
|
||||
<< std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout
|
||||
<< "configure the license server url, please include http[s] in the url"
|
||||
<< std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " default keyid:";
|
||||
std::cout << g_key_id << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
show_menu(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_config = new wvcdm::ConfigTestEnv(g_license_server_id);
|
||||
g_client_auth.assign(g_config->client_auth());
|
||||
g_key_system.assign(g_config->key_system());
|
||||
g_wrong_key_id.assign(g_config->wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line
|
||||
// options. If the command line arguments are absent, use the settings
|
||||
// in license_servers[] pointed to by g_config.
|
||||
if (g_key_id.empty()) {
|
||||
g_key_id.assign(g_config->key_id());
|
||||
}
|
||||
if (g_license_server.empty()) {
|
||||
g_license_server.assign(g_config->license_server());
|
||||
}
|
||||
if (g_port.empty()) {
|
||||
g_port.assign(g_config->port());
|
||||
}
|
||||
if (!g_use_chunked_transfer) {
|
||||
g_use_chunked_transfer = g_config->use_chunked_transfer();
|
||||
}
|
||||
if (!g_use_secure_transfer) {
|
||||
g_use_secure_transfer = g_config->use_secure_transfer();
|
||||
}
|
||||
|
||||
// Displays server url, port and key Id being used
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
|
||||
|
||||
g_key_id = wvcdm::a2bs_hex(g_key_id);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id);
|
||||
g_config->set_license_server(g_license_server);
|
||||
g_config->set_port(g_port);
|
||||
g_config->set_key_id(g_key_id);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
int status = RUN_ALL_TESTS();
|
||||
delete g_config;
|
||||
return status;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user