/////////////////////////////////////////////////////////////////////////////// // // Project: C++ Test Suite for GDAL/OGR // Purpose: Test general CPL features. // Author: Mateusz Loskot // /////////////////////////////////////////////////////////////////////////////// // Copyright (c) 2006, Mateusz Loskot // Copyright (c) 2008-2012, Even Rouault // Copyright (c) 2017, Dmitry Baryshnikov // Copyright (c) 2017, NextGIS /* * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef GDAL_COMPILATION #define GDAL_COMPILATION #endif #include "gdal_unit_test.h" #include "cpl_compressor.h" #include "cpl_error.h" #include "cpl_hash_set.h" #include "cpl_list.h" #include "cpl_mask.h" #include "cpl_sha256.h" #include "cpl_string.h" #include "cpl_safemaths.hpp" #include "cpl_time.h" #include "cpl_json.h" #include "cpl_json_streaming_parser.h" #include "cpl_json_streaming_writer.h" #include "cpl_mem_cache.h" #include "cpl_http.h" #include "cpl_auto_close.h" #include "cpl_minixml.h" #include "cpl_quad_tree.h" #include "cpl_worker_thread_pool.h" #include "cpl_vsi_virtual.h" #include "cpl_threadsafe_queue.hpp" #include #include #include #include #include "gtest_include.h" static bool gbGotError = false; static void CPL_STDCALL myErrorHandler(CPLErr, CPLErrorNum, const char *) { gbGotError = true; } namespace { // Common fixture with test data struct test_cpl : public ::testing::Test { std::string data_; test_cpl() { // Compose data path for test group data_ = tut::common::data_basedir; } void SetUp() override { CPLSetConfigOptions(nullptr); CPLSetThreadLocalConfigOptions(nullptr); } }; // Test cpl_list API TEST_F(test_cpl, CPLList) { CPLList *list; list = CPLListInsert(nullptr, (void *)nullptr, 0); EXPECT_TRUE(CPLListCount(list) == 1); list = CPLListRemove(list, 2); EXPECT_TRUE(CPLListCount(list) == 1); list = CPLListRemove(list, 1); EXPECT_TRUE(CPLListCount(list) == 1); list = CPLListRemove(list, 0); EXPECT_TRUE(CPLListCount(list) == 0); list = nullptr; list = CPLListInsert(nullptr, (void *)nullptr, 2); EXPECT_TRUE(CPLListCount(list) == 3); list = CPLListRemove(list, 2); EXPECT_TRUE(CPLListCount(list) == 2); list = CPLListRemove(list, 1); EXPECT_TRUE(CPLListCount(list) == 1); list = CPLListRemove(list, 0); EXPECT_TRUE(CPLListCount(list) == 0); list = nullptr; list = CPLListAppend(list, (void *)1); EXPECT_TRUE(CPLListGet(list, 0) == list); EXPECT_TRUE(CPLListGet(list, 1) == nullptr); list = CPLListAppend(list, (void *)2); list = CPLListInsert(list, (void *)3, 2); EXPECT_TRUE(CPLListCount(list) == 3); CPLListDestroy(list); list = nullptr; list = CPLListAppend(list, (void *)1); list = CPLListAppend(list, (void *)2); list = CPLListInsert(list, (void *)4, 3); CPLListGet(list, 2)->pData = (void *)3; EXPECT_TRUE(CPLListCount(list) == 4); EXPECT_TRUE(CPLListGet(list, 0)->pData == (void *)1); EXPECT_TRUE(CPLListGet(list, 1)->pData == (void *)2); EXPECT_TRUE(CPLListGet(list, 2)->pData == (void *)3); EXPECT_TRUE(CPLListGet(list, 3)->pData == (void *)4); CPLListDestroy(list); list = nullptr; list = CPLListInsert(list, (void *)4, 1); CPLListGet(list, 0)->pData = (void *)2; list = CPLListInsert(list, (void *)1, 0); list = CPLListInsert(list, (void *)3, 2); EXPECT_TRUE(CPLListCount(list) == 4); EXPECT_TRUE(CPLListGet(list, 0)->pData == (void *)1); EXPECT_TRUE(CPLListGet(list, 1)->pData == (void *)2); EXPECT_TRUE(CPLListGet(list, 2)->pData == (void *)3); EXPECT_TRUE(CPLListGet(list, 3)->pData == (void *)4); list = CPLListRemove(list, 1); list = CPLListRemove(list, 1); list = CPLListRemove(list, 0); list = CPLListRemove(list, 0); EXPECT_TRUE(list == nullptr); } typedef struct { const char *testString; CPLValueType expectedResult; } TestStringStruct; // Test CPLGetValueType TEST_F(test_cpl, CPLGetValueType) { TestStringStruct apszTestStrings[] = { {"+25.e+3", CPL_VALUE_REAL}, {"-25.e-3", CPL_VALUE_REAL}, {"25.e3", CPL_VALUE_REAL}, {"25e3", CPL_VALUE_REAL}, {" 25e3 ", CPL_VALUE_REAL}, {".1e3", CPL_VALUE_REAL}, {"25", CPL_VALUE_INTEGER}, {"-25", CPL_VALUE_INTEGER}, {"+25", CPL_VALUE_INTEGER}, {"25e 3", CPL_VALUE_STRING}, {"25e.3", CPL_VALUE_STRING}, {"-2-5e3", CPL_VALUE_STRING}, {"2-5e3", CPL_VALUE_STRING}, {"25.25.3", CPL_VALUE_STRING}, {"25e25e3", CPL_VALUE_STRING}, {"25e2500", CPL_VALUE_STRING}, /* #6128 */ {"d1", CPL_VALUE_STRING} /* #6305 */ }; size_t i; for (i = 0; i < sizeof(apszTestStrings) / sizeof(apszTestStrings[0]); i++) { EXPECT_EQ(CPLGetValueType(apszTestStrings[i].testString), apszTestStrings[i].expectedResult) << apszTestStrings[i].testString; } } // Test cpl_hash_set API TEST_F(test_cpl, CPLHashSet) { CPLHashSet *set = CPLHashSetNew(CPLHashSetHashStr, CPLHashSetEqualStr, CPLFree); EXPECT_TRUE(CPLHashSetInsert(set, CPLStrdup("hello")) == TRUE); EXPECT_TRUE(CPLHashSetInsert(set, CPLStrdup("good morning")) == TRUE); EXPECT_TRUE(CPLHashSetInsert(set, CPLStrdup("bye bye")) == TRUE); EXPECT_TRUE(CPLHashSetSize(set) == 3); EXPECT_TRUE(CPLHashSetInsert(set, CPLStrdup("bye bye")) == FALSE); EXPECT_TRUE(CPLHashSetSize(set) == 3); EXPECT_TRUE(CPLHashSetRemove(set, "bye bye") == TRUE); EXPECT_TRUE(CPLHashSetSize(set) == 2); EXPECT_TRUE(CPLHashSetRemove(set, "good afternoon") == FALSE); EXPECT_TRUE(CPLHashSetSize(set) == 2); CPLHashSetDestroy(set); } static int sumValues(void *elt, void *user_data) { int *pnSum = (int *)user_data; *pnSum += *(int *)elt; return TRUE; } // Test cpl_hash_set API TEST_F(test_cpl, CPLHashSet2) { const int HASH_SET_SIZE = 1000; int data[HASH_SET_SIZE]; for (int i = 0; i < HASH_SET_SIZE; ++i) { data[i] = i; } CPLHashSet *set = CPLHashSetNew(nullptr, nullptr, nullptr); for (int i = 0; i < HASH_SET_SIZE; i++) { EXPECT_TRUE(CPLHashSetInsert(set, (void *)&data[i]) == TRUE); } EXPECT_EQ(CPLHashSetSize(set), HASH_SET_SIZE); for (int i = 0; i < HASH_SET_SIZE; i++) { EXPECT_TRUE(CPLHashSetInsert(set, (void *)&data[i]) == FALSE); } EXPECT_EQ(CPLHashSetSize(set), HASH_SET_SIZE); for (int i = 0; i < HASH_SET_SIZE; i++) { EXPECT_TRUE(CPLHashSetLookup(set, (const void *)&data[i]) == (const void *)&data[i]); } int sum = 0; CPLHashSetForeach(set, sumValues, &sum); EXPECT_EQ(sum, (HASH_SET_SIZE - 1) * HASH_SET_SIZE / 2); for (int i = 0; i < HASH_SET_SIZE; i++) { EXPECT_TRUE(CPLHashSetRemove(set, (void *)&data[i]) == TRUE); } EXPECT_EQ(CPLHashSetSize(set), 0); CPLHashSetDestroy(set); } // Test cpl_string API TEST_F(test_cpl, CSLTokenizeString2) { { CPLStringList aosStringList( CSLTokenizeString2("one two three", " ", 0)); ASSERT_EQ(aosStringList.size(), 3); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "three")); // Test range-based for loop int i = 0; for (const char *pszVal : aosStringList) { EXPECT_STREQ(pszVal, aosStringList[i]); ++i; } EXPECT_EQ(i, 3); } { CPLStringList aosStringList; // Test range-based for loop on empty list int i = 0; for (const char *pszVal : aosStringList) { EXPECT_EQ(pszVal, nullptr); // should not reach that point... ++i; } EXPECT_EQ(i, 0); } { CPLStringList aosStringList( CSLTokenizeString2("one two, three;four,five; six", " ;,", 0)); ASSERT_EQ(aosStringList.size(), 6); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "three")); ASSERT_TRUE(EQUAL(aosStringList[3], "four")); ASSERT_TRUE(EQUAL(aosStringList[4], "five")); ASSERT_TRUE(EQUAL(aosStringList[5], "six")); } { CPLStringList aosStringList(CSLTokenizeString2( "one two,,,five,six", " ,", CSLT_ALLOWEMPTYTOKENS)); ASSERT_EQ(aosStringList.size(), 6); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "")); ASSERT_TRUE(EQUAL(aosStringList[3], "")); ASSERT_TRUE(EQUAL(aosStringList[4], "five")); ASSERT_TRUE(EQUAL(aosStringList[5], "six")); } { CPLStringList aosStringList(CSLTokenizeString2( "one two,\"three,four ,\",five,six", " ,", CSLT_HONOURSTRINGS)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "three,four ,")); ASSERT_TRUE(EQUAL(aosStringList[3], "five")); ASSERT_TRUE(EQUAL(aosStringList[4], "six")); } { CPLStringList aosStringList(CSLTokenizeString2( "one two,\"three,four ,\",five,six", " ,", CSLT_PRESERVEQUOTES)); ASSERT_EQ(aosStringList.size(), 7); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "\"three")); ASSERT_TRUE(EQUAL(aosStringList[3], "four")); ASSERT_TRUE(EQUAL(aosStringList[4], "\"")); ASSERT_TRUE(EQUAL(aosStringList[5], "five")); ASSERT_TRUE(EQUAL(aosStringList[6], "six")); } { CPLStringList aosStringList( CSLTokenizeString2("one two,\"three,four ,\",five,six", " ,", CSLT_HONOURSTRINGS | CSLT_PRESERVEQUOTES)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "\"three,four ,\"")); ASSERT_TRUE(EQUAL(aosStringList[3], "five")); ASSERT_TRUE(EQUAL(aosStringList[4], "six")); } { CPLStringList aosStringList( CSLTokenizeString2("one \\two,\"three,\\four ,\",five,six", " ,", CSLT_PRESERVEESCAPES)); ASSERT_EQ(aosStringList.size(), 7); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "\\two")); ASSERT_TRUE(EQUAL(aosStringList[2], "\"three")); ASSERT_TRUE(EQUAL(aosStringList[3], "\\four")); ASSERT_TRUE(EQUAL(aosStringList[4], "\"")); ASSERT_TRUE(EQUAL(aosStringList[5], "five")); ASSERT_TRUE(EQUAL(aosStringList[6], "six")); } { CPLStringList aosStringList( CSLTokenizeString2("one \\two,\"three,\\four ,\",five,six", " ,", CSLT_PRESERVEQUOTES | CSLT_PRESERVEESCAPES)); ASSERT_EQ(aosStringList.size(), 7); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "\\two")); ASSERT_TRUE(EQUAL(aosStringList[2], "\"three")); ASSERT_TRUE(EQUAL(aosStringList[3], "\\four")); ASSERT_TRUE(EQUAL(aosStringList[4], "\"")); ASSERT_TRUE(EQUAL(aosStringList[5], "five")); ASSERT_TRUE(EQUAL(aosStringList[6], "six")); } { CPLStringList aosStringList( CSLTokenizeString2("one ,two, three, four ,five ", ",", 0)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one ")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], " three")); ASSERT_TRUE(EQUAL(aosStringList[3], " four ")); ASSERT_TRUE(EQUAL(aosStringList[4], "five ")); } { CPLStringList aosStringList(CSLTokenizeString2( "one ,two, three, four ,five ", ",", CSLT_STRIPLEADSPACES)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one ")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "three")); ASSERT_TRUE(EQUAL(aosStringList[3], "four ")); ASSERT_TRUE(EQUAL(aosStringList[4], "five ")); } { CPLStringList aosStringList(CSLTokenizeString2( "one ,two, three, four ,five ", ",", CSLT_STRIPENDSPACES)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], " three")); ASSERT_TRUE(EQUAL(aosStringList[3], " four")); ASSERT_TRUE(EQUAL(aosStringList[4], "five")); } { CPLStringList aosStringList( CSLTokenizeString2("one ,two, three, four ,five ", ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); ASSERT_EQ(aosStringList.size(), 5); ASSERT_TRUE(EQUAL(aosStringList[0], "one")); ASSERT_TRUE(EQUAL(aosStringList[1], "two")); ASSERT_TRUE(EQUAL(aosStringList[2], "three")); ASSERT_TRUE(EQUAL(aosStringList[3], "four")); ASSERT_TRUE(EQUAL(aosStringList[4], "five")); } } typedef struct { char szEncoding[24]; char szString[1024 - 24]; } TestRecodeStruct; // Test cpl_recode API TEST_F(test_cpl, CPLRecode) { /* * NOTE: This test will generally fail if iconv() is not * linked in. * * CPLRecode() will be tested using the test file containing * a list of strings of the same text in different encoding. The * string is non-ASCII to avoid trivial transformations. Test file * has a simple binary format: a table of records, each record * is 1024 bytes long. The first 24 bytes of each record contain * encoding name (ASCII, zero padded), the last 1000 bytes contain * encoded string, zero padded. * * NOTE 1: We can't use a test file in human readable text format * here because of multiple different encodings including * multibyte ones. * * The test file could be generated with the following simple shell * script: * * #!/bin/sh * * # List of encodings to convert the test string into * ENCODINGS="UTF-8 CP1251 KOI8-R UCS-2 UCS-2BE UCS-2LE UCS-4 UCS-4BE * UCS-4LE UTF-16 UTF-32" # The test string itself in UTF-8 encoding. # This * means "Improving GDAL internationalization." in Russian. * TESTSTRING="\u0423\u043b\u0443\u0447\u0448\u0430\u0435\u043c * \u0438\u043d\u0442\u0435\u0440\u043d\u0430\u0446\u0438\u043e\u043d\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e * GDAL." * * RECORDSIZE=1024 * ENCSIZE=24 * * i=0 * for enc in ${ENCODINGS}; do * env printf "${enc}" | dd ibs=${RECORDSIZE} conv=sync obs=1 * seek=$((${RECORDSIZE}*${i})) of="recode-rus.dat" status=noxfer env printf * "${TESTSTRING}" | iconv -t ${enc} | dd ibs=${RECORDSIZE} conv=sync obs=1 * seek=$((${RECORDSIZE}*${i}+${ENCSIZE})) of="recode-rus.dat" status=noxfer * i=$((i+1)) * done * * NOTE 2: The test string is encoded with the special format * "\uXXXX" sequences, so we able to paste it here. * * NOTE 3: We need a printf utility from the coreutils because of * that. "env printf" should work avoiding the shell * built-in. * * NOTE 4: "iconv" utility without the "-f" option will work with * encoding read from the current locale. * * TODO: 1. Add more encodings maybe more test files. * 2. Add test for CPLRecodeFromWChar()/CPLRecodeToWChar(). * 3. Test translation between each possible pair of * encodings in file, not only into the UTF-8. */ std::ifstream fin((data_ + SEP + "recode-rus.dat").c_str(), std::ifstream::binary); TestRecodeStruct oReferenceString; // Read reference string (which is the first one in the file) fin.read(oReferenceString.szEncoding, sizeof(oReferenceString.szEncoding)); oReferenceString.szEncoding[sizeof(oReferenceString.szEncoding) - 1] = '\0'; fin.read(oReferenceString.szString, sizeof(oReferenceString.szString)); oReferenceString.szString[sizeof(oReferenceString.szString) - 1] = '\0'; while (true) { TestRecodeStruct oTestString; fin.read(oTestString.szEncoding, sizeof(oTestString.szEncoding)); oTestString.szEncoding[sizeof(oTestString.szEncoding) - 1] = '\0'; if (fin.eof()) break; fin.read(oTestString.szString, sizeof(oTestString.szString)); oTestString.szString[sizeof(oTestString.szString) - 1] = '\0'; // Compare each string with the reference one CPLErrorReset(); char *pszDecodedString = CPLRecode(oTestString.szString, oTestString.szEncoding, oReferenceString.szEncoding); if (strstr(CPLGetLastErrorMsg(), "Recode from CP1251 to UTF-8 not supported") != nullptr || strstr(CPLGetLastErrorMsg(), "Recode from KOI8-R to UTF-8 not supported") != nullptr) { CPLFree(pszDecodedString); break; } size_t nLength = MIN(strlen(pszDecodedString), sizeof(oReferenceString.szEncoding)); bool bOK = (memcmp(pszDecodedString, oReferenceString.szString, nLength) == 0); // FIXME Some tests fail on Mac. Not sure why, but do not error out just // for that if (!bOK && (strstr(CPLGetConfigOption("TRAVIS_OS_NAME", ""), "osx") != nullptr || strstr(CPLGetConfigOption("BUILD_NAME", ""), "osx") != nullptr || getenv("DO_NOT_FAIL_ON_RECODE_ERRORS") != nullptr)) { fprintf(stderr, "Recode from %s failed\n", oTestString.szEncoding); } else { #ifdef CPL_MSB if (!bOK && strcmp(oTestString.szEncoding, "UCS-2") == 0) { // Presumably the content in the test file is UCS-2LE, but // there's no way to know the byte order without a BOM fprintf(stderr, "Recode from %s failed\n", oTestString.szEncoding); } else #endif { EXPECT_TRUE(bOK) << "Recode from " << oTestString.szEncoding; } } CPLFree(pszDecodedString); } fin.close(); } /************************************************************************/ /* CPLStringList tests */ /************************************************************************/ TEST_F(test_cpl, CPLStringList_Base) { CPLStringList oCSL; ASSERT_TRUE(oCSL.List() == nullptr); oCSL.AddString("def"); oCSL.AddString("abc"); ASSERT_EQ(oCSL.Count(), 2); ASSERT_TRUE(EQUAL(oCSL[0], "def")); ASSERT_TRUE(EQUAL(oCSL[1], "abc")); ASSERT_TRUE(oCSL[17] == nullptr); ASSERT_TRUE(oCSL[-1] == nullptr); ASSERT_EQ(oCSL.FindString("abc"), 1); CSLDestroy(oCSL.StealList()); ASSERT_EQ(oCSL.Count(), 0); ASSERT_TRUE(oCSL.List() == nullptr); // Test that the list will make an internal copy when needed to // modify a read-only list. oCSL.AddString("def"); oCSL.AddString("abc"); CPLStringList oCopy(oCSL.List(), FALSE); ASSERT_EQ(oCSL.List(), oCopy.List()); ASSERT_EQ(oCSL.Count(), oCopy.Count()); oCopy.AddString("xyz"); ASSERT_TRUE(oCSL.List() != oCopy.List()); ASSERT_EQ(oCopy.Count(), 3); ASSERT_EQ(oCSL.Count(), 2); ASSERT_TRUE(EQUAL(oCopy[2], "xyz")); } TEST_F(test_cpl, CPLStringList_NameValue) { // Test some name=value handling stuff. CPLStringList oNVL; oNVL.AddNameValue("KEY1", "VALUE1"); oNVL.AddNameValue("2KEY", "VALUE2"); ASSERT_EQ(oNVL.Count(), 2); ASSERT_TRUE(EQUAL(oNVL.FetchNameValue("2KEY"), "VALUE2")); ASSERT_TRUE(oNVL.FetchNameValue("MISSING") == nullptr); oNVL.AddNameValue("KEY1", "VALUE3"); ASSERT_TRUE(EQUAL(oNVL.FetchNameValue("KEY1"), "VALUE1")); ASSERT_TRUE(EQUAL(oNVL[2], "KEY1=VALUE3")); ASSERT_TRUE(EQUAL(oNVL.FetchNameValueDef("MISSING", "X"), "X")); oNVL.SetNameValue("2KEY", "VALUE4"); ASSERT_TRUE(EQUAL(oNVL.FetchNameValue("2KEY"), "VALUE4")); ASSERT_EQ(oNVL.Count(), 3); // make sure deletion works. oNVL.SetNameValue("2KEY", nullptr); ASSERT_TRUE(oNVL.FetchNameValue("2KEY") == nullptr); ASSERT_EQ(oNVL.Count(), 2); // Test boolean support. ASSERT_EQ(oNVL.FetchBoolean("BOOL", TRUE), TRUE); ASSERT_EQ(oNVL.FetchBoolean("BOOL", FALSE), FALSE); oNVL.SetNameValue("BOOL", "YES"); ASSERT_EQ(oNVL.FetchBoolean("BOOL", TRUE), TRUE); ASSERT_EQ(oNVL.FetchBoolean("BOOL", FALSE), TRUE); oNVL.SetNameValue("BOOL", "1"); ASSERT_EQ(oNVL.FetchBoolean("BOOL", FALSE), TRUE); oNVL.SetNameValue("BOOL", "0"); ASSERT_EQ(oNVL.FetchBoolean("BOOL", TRUE), FALSE); oNVL.SetNameValue("BOOL", "FALSE"); ASSERT_EQ(oNVL.FetchBoolean("BOOL", TRUE), FALSE); oNVL.SetNameValue("BOOL", "ON"); ASSERT_EQ(oNVL.FetchBoolean("BOOL", FALSE), TRUE); // Test assignment operator. CPLStringList oCopy; { CPLStringList oTemp; oTemp.AddString("test"); oCopy = oTemp; } EXPECT_STREQ(oCopy[0], "test"); auto &oCopyRef(oCopy); oCopy = oCopyRef; EXPECT_STREQ(oCopy[0], "test"); // Test copy constructor. CPLStringList oCopy2(oCopy); EXPECT_EQ(oCopy2.Count(), oCopy.Count()); oCopy.Clear(); EXPECT_STREQ(oCopy2[0], "test"); // Test move constructor CPLStringList oMoved(std::move(oCopy2)); EXPECT_STREQ(oMoved[0], "test"); // Test move assignment operator CPLStringList oMoved2; oMoved2 = std::move(oMoved); EXPECT_STREQ(oMoved2[0], "test"); // Test sorting CPLStringList oTestSort; oTestSort.AddNameValue("Z", "1"); oTestSort.AddNameValue("L", "2"); oTestSort.AddNameValue("T", "3"); oTestSort.AddNameValue("A", "4"); oTestSort.Sort(); EXPECT_STREQ(oTestSort[0], "A=4"); EXPECT_STREQ(oTestSort[1], "L=2"); EXPECT_STREQ(oTestSort[2], "T=3"); EXPECT_STREQ(oTestSort[3], "Z=1"); ASSERT_EQ(oTestSort[4], (const char *)nullptr); // Test FetchNameValue() in a sorted list EXPECT_STREQ(oTestSort.FetchNameValue("A"), "4"); EXPECT_STREQ(oTestSort.FetchNameValue("L"), "2"); EXPECT_STREQ(oTestSort.FetchNameValue("T"), "3"); EXPECT_STREQ(oTestSort.FetchNameValue("Z"), "1"); // Test AddNameValue() in a sorted list oTestSort.AddNameValue("B", "5"); EXPECT_STREQ(oTestSort[0], "A=4"); EXPECT_STREQ(oTestSort[1], "B=5"); EXPECT_STREQ(oTestSort[2], "L=2"); EXPECT_STREQ(oTestSort[3], "T=3"); EXPECT_STREQ(oTestSort[4], "Z=1"); ASSERT_EQ(oTestSort[5], (const char *)nullptr); // Test SetNameValue() of an existing item in a sorted list oTestSort.SetNameValue("Z", "6"); EXPECT_STREQ(oTestSort[4], "Z=6"); // Test SetNameValue() of a non-existing item in a sorted list oTestSort.SetNameValue("W", "7"); EXPECT_STREQ(oTestSort[0], "A=4"); EXPECT_STREQ(oTestSort[1], "B=5"); EXPECT_STREQ(oTestSort[2], "L=2"); EXPECT_STREQ(oTestSort[3], "T=3"); EXPECT_STREQ(oTestSort[4], "W=7"); EXPECT_STREQ(oTestSort[5], "Z=6"); ASSERT_EQ(oTestSort[6], (const char *)nullptr); } TEST_F(test_cpl, CPLStringList_Sort) { // Test some name=value handling stuff *with* sorting active. CPLStringList oNVL; oNVL.Sort(); oNVL.AddNameValue("KEY1", "VALUE1"); oNVL.AddNameValue("2KEY", "VALUE2"); ASSERT_EQ(oNVL.Count(), 2); EXPECT_STREQ(oNVL.FetchNameValue("KEY1"), "VALUE1"); EXPECT_STREQ(oNVL.FetchNameValue("2KEY"), "VALUE2"); ASSERT_TRUE(oNVL.FetchNameValue("MISSING") == nullptr); oNVL.AddNameValue("KEY1", "VALUE3"); ASSERT_EQ(oNVL.Count(), 3); EXPECT_STREQ(oNVL.FetchNameValue("KEY1"), "VALUE1"); EXPECT_STREQ(oNVL.FetchNameValueDef("MISSING", "X"), "X"); oNVL.SetNameValue("2KEY", "VALUE4"); EXPECT_STREQ(oNVL.FetchNameValue("2KEY"), "VALUE4"); ASSERT_EQ(oNVL.Count(), 3); // make sure deletion works. oNVL.SetNameValue("2KEY", nullptr); ASSERT_TRUE(oNVL.FetchNameValue("2KEY") == nullptr); ASSERT_EQ(oNVL.Count(), 2); // Test insertion logic pretty carefully. oNVL.Clear(); ASSERT_TRUE(oNVL.IsSorted() == TRUE); oNVL.SetNameValue("B", "BB"); oNVL.SetNameValue("A", "AA"); oNVL.SetNameValue("D", "DD"); oNVL.SetNameValue("C", "CC"); // items should be in sorted order. EXPECT_STREQ(oNVL[0], "A=AA"); EXPECT_STREQ(oNVL[1], "B=BB"); EXPECT_STREQ(oNVL[2], "C=CC"); EXPECT_STREQ(oNVL[3], "D=DD"); EXPECT_STREQ(oNVL.FetchNameValue("A"), "AA"); EXPECT_STREQ(oNVL.FetchNameValue("B"), "BB"); EXPECT_STREQ(oNVL.FetchNameValue("C"), "CC"); EXPECT_STREQ(oNVL.FetchNameValue("D"), "DD"); } TEST_F(test_cpl, CPL_HMAC_SHA256) { GByte abyDigest[CPL_SHA256_HASH_SIZE]; char szDigest[2 * CPL_SHA256_HASH_SIZE + 1]; CPL_HMAC_SHA256("key", 3, "The quick brown fox jumps over the lazy dog", strlen("The quick brown fox jumps over the lazy dog"), abyDigest); for (int i = 0; i < CPL_SHA256_HASH_SIZE; i++) snprintf(szDigest + 2 * i, sizeof(szDigest) - 2 * i, "%02x", abyDigest[i]); // fprintf(stderr, "%s\n", szDigest); EXPECT_STREQ( szDigest, "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); CPL_HMAC_SHA256( "mysupersupersupersupersupersupersupersupersupersupersupersupersupersup" "ersupersupersupersupersupersuperlongkey", strlen("mysupersupersupersupersupersupersupersupersupersupersupersupers" "upersupersupersupersupersupersupersuperlongkey"), "msg", 3, abyDigest); for (int i = 0; i < CPL_SHA256_HASH_SIZE; i++) snprintf(szDigest + 2 * i, sizeof(szDigest) - 2 * i, "%02x", abyDigest[i]); // fprintf(stderr, "%s\n", szDigest); EXPECT_STREQ( szDigest, "a3051520761ed3cb43876b35ce2dd93ac5b332dc3bad898bb32086f7ac71ffc1"); } TEST_F(test_cpl, VSIMalloc) { CPLPushErrorHandler(CPLQuietErrorHandler); // The following tests will fail because of overflows CPLErrorReset(); ASSERT_TRUE(VSIMalloc2(~(size_t)0, ~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSIMalloc3(1, ~(size_t)0, ~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSIMalloc3(~(size_t)0, 1, ~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSIMalloc3(~(size_t)0, ~(size_t)0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); if (!CSLTestBoolean(CPLGetConfigOption("SKIP_MEM_INTENSIVE_TEST", "NO"))) { // The following tests will fail because such allocations cannot succeed #if SIZEOF_VOIDP == 8 CPLErrorReset(); ASSERT_TRUE(VSIMalloc(~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() == CE_None); /* no error reported */ CPLErrorReset(); ASSERT_TRUE(VSIMalloc2(~(size_t)0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSIMalloc3(~(size_t)0, 1, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSICalloc(~(size_t)0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() == CE_None); /* no error reported */ CPLErrorReset(); ASSERT_TRUE(VSIRealloc(nullptr, ~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() == CE_None); /* no error reported */ CPLErrorReset(); ASSERT_TRUE(VSI_MALLOC_VERBOSE(~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSI_MALLOC2_VERBOSE(~(size_t)0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSI_MALLOC3_VERBOSE(~(size_t)0, 1, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSI_CALLOC_VERBOSE(~(size_t)0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); CPLErrorReset(); ASSERT_TRUE(VSI_REALLOC_VERBOSE(nullptr, ~(size_t)0) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() != CE_None); #endif } CPLPopErrorHandler(); // The following allocs will return NULL because of 0 byte alloc CPLErrorReset(); ASSERT_TRUE(VSIMalloc2(0, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() == CE_None); ASSERT_TRUE(VSIMalloc2(1, 0) == nullptr); CPLErrorReset(); ASSERT_TRUE(VSIMalloc3(0, 1, 1) == nullptr); ASSERT_TRUE(CPLGetLastErrorType() == CE_None); ASSERT_TRUE(VSIMalloc3(1, 0, 1) == nullptr); ASSERT_TRUE(VSIMalloc3(1, 1, 0) == nullptr); } TEST_F(test_cpl, CPLFormFilename) { EXPECT_TRUE(strcmp(CPLFormFilename("a", "b", nullptr), "a/b") == 0 || strcmp(CPLFormFilename("a", "b", nullptr), "a\\b") == 0); EXPECT_TRUE(strcmp(CPLFormFilename("a/", "b", nullptr), "a/b") == 0 || strcmp(CPLFormFilename("a/", "b", nullptr), "a\\b") == 0); EXPECT_TRUE(strcmp(CPLFormFilename("a\\", "b", nullptr), "a/b") == 0 || strcmp(CPLFormFilename("a\\", "b", nullptr), "a\\b") == 0); EXPECT_STREQ(CPLFormFilename(nullptr, "a", "b"), "a.b"); EXPECT_STREQ(CPLFormFilename(nullptr, "a", ".b"), "a.b"); EXPECT_STREQ(CPLFormFilename("/a", "..", nullptr), "/"); EXPECT_STREQ(CPLFormFilename("/a/", "..", nullptr), "/"); EXPECT_STREQ(CPLFormFilename("/a/b", "..", nullptr), "/a"); EXPECT_STREQ(CPLFormFilename("/a/b/", "..", nullptr), "/a"); EXPECT_TRUE(EQUAL(CPLFormFilename("c:", "..", nullptr), "c:/..") || EQUAL(CPLFormFilename("c:", "..", nullptr), "c:\\..")); EXPECT_TRUE(EQUAL(CPLFormFilename("c:\\", "..", nullptr), "c:/..") || EQUAL(CPLFormFilename("c:\\", "..", nullptr), "c:\\..")); EXPECT_STREQ(CPLFormFilename("c:\\a", "..", nullptr), "c:"); EXPECT_STREQ(CPLFormFilename("c:\\a\\", "..", nullptr), "c:"); EXPECT_STREQ(CPLFormFilename("c:\\a\\b", "..", nullptr), "c:\\a"); EXPECT_STREQ(CPLFormFilename("\\\\$\\c:\\a", "..", nullptr), "\\\\$\\c:"); EXPECT_TRUE( EQUAL(CPLFormFilename("\\\\$\\c:", "..", nullptr), "\\\\$\\c:/..") || EQUAL(CPLFormFilename("\\\\$\\c:", "..", nullptr), "\\\\$\\c:\\..")); } TEST_F(test_cpl, VSIGetDiskFreeSpace) { ASSERT_TRUE(VSIGetDiskFreeSpace("/vsimem/") > 0); ASSERT_TRUE(VSIGetDiskFreeSpace(".") == -1 || VSIGetDiskFreeSpace(".") >= 0); } TEST_F(test_cpl, CPLsscanf) { double a, b, c; a = b = 0; ASSERT_EQ(CPLsscanf("1 2", "%lf %lf", &a, &b), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); a = b = 0; ASSERT_EQ(CPLsscanf("1\t2", "%lf %lf", &a, &b), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); a = b = 0; ASSERT_EQ(CPLsscanf("1 2", "%lf\t%lf", &a, &b), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); a = b = 0; ASSERT_EQ(CPLsscanf("1 2", "%lf %lf", &a, &b), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); a = b = 0; ASSERT_EQ(CPLsscanf("1 2", "%lf %lf", &a, &b), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); a = b = c = 0; ASSERT_EQ(CPLsscanf("1 2", "%lf %lf %lf", &a, &b, &c), 2); ASSERT_EQ(a, 1.0); ASSERT_EQ(b, 2.0); } TEST_F(test_cpl, CPLSetErrorHandler) { CPLString oldVal = CPLGetConfigOption("CPL_DEBUG", ""); CPLSetConfigOption("CPL_DEBUG", "TEST"); CPLErrorHandler oldHandler = CPLSetErrorHandler(myErrorHandler); gbGotError = false; CPLDebug("TEST", "Test"); ASSERT_EQ(gbGotError, true); gbGotError = false; CPLSetErrorHandler(oldHandler); CPLPushErrorHandler(myErrorHandler); gbGotError = false; CPLDebug("TEST", "Test"); ASSERT_EQ(gbGotError, true); gbGotError = false; CPLPopErrorHandler(); oldHandler = CPLSetErrorHandler(myErrorHandler); CPLSetCurrentErrorHandlerCatchDebug(FALSE); gbGotError = false; CPLDebug("TEST", "Test"); ASSERT_EQ(gbGotError, false); gbGotError = false; CPLSetErrorHandler(oldHandler); CPLPushErrorHandler(myErrorHandler); CPLSetCurrentErrorHandlerCatchDebug(FALSE); gbGotError = false; CPLDebug("TEST", "Test"); ASSERT_EQ(gbGotError, false); gbGotError = false; CPLPopErrorHandler(); CPLSetConfigOption("CPL_DEBUG", oldVal.size() ? oldVal.c_str() : nullptr); oldHandler = CPLSetErrorHandler(nullptr); CPLDebug("TEST", "Test"); CPLError(CE_Failure, CPLE_AppDefined, "test"); CPLErrorHandler newOldHandler = CPLSetErrorHandler(nullptr); ASSERT_EQ(newOldHandler, static_cast(nullptr)); CPLDebug("TEST", "Test"); CPLError(CE_Failure, CPLE_AppDefined, "test"); CPLSetErrorHandler(oldHandler); } /************************************************************************/ /* CPLString::replaceAll() */ /************************************************************************/ TEST_F(test_cpl, CPLString_replaceAll) { CPLString osTest; osTest = "foobarbarfoo"; osTest.replaceAll("bar", "was_bar"); ASSERT_EQ(osTest, "foowas_barwas_barfoo"); osTest = "foobarbarfoo"; osTest.replaceAll("X", "was_bar"); ASSERT_EQ(osTest, "foobarbarfoo"); osTest = "foobarbarfoo"; osTest.replaceAll("", "was_bar"); ASSERT_EQ(osTest, "foobarbarfoo"); osTest = "foobarbarfoo"; osTest.replaceAll("bar", ""); ASSERT_EQ(osTest, "foofoo"); osTest = "foobarbarfoo"; osTest.replaceAll('b', 'B'); ASSERT_EQ(osTest, "fooBarBarfoo"); osTest = "foobarbarfoo"; osTest.replaceAll('b', "B"); ASSERT_EQ(osTest, "fooBarBarfoo"); osTest = "foobarbarfoo"; osTest.replaceAll("b", 'B'); ASSERT_EQ(osTest, "fooBarBarfoo"); } /************************************************************************/ /* VSIMallocAligned() */ /************************************************************************/ TEST_F(test_cpl, VSIMallocAligned) { GByte *ptr = static_cast(VSIMallocAligned(sizeof(void *), 1)); ASSERT_TRUE(ptr != nullptr); ASSERT_TRUE(((size_t)ptr % sizeof(void *)) == 0); *ptr = 1; VSIFreeAligned(ptr); ptr = static_cast(VSIMallocAligned(16, 1)); ASSERT_TRUE(ptr != nullptr); ASSERT_TRUE(((size_t)ptr % 16) == 0); *ptr = 1; VSIFreeAligned(ptr); VSIFreeAligned(nullptr); #ifndef WIN32 // Illegal use of API. Returns non NULL on Windows ptr = static_cast(VSIMallocAligned(2, 1)); EXPECT_TRUE(ptr == nullptr); VSIFree(ptr); // Illegal use of API. Crashes on Windows ptr = static_cast(VSIMallocAligned(5, 1)); EXPECT_TRUE(ptr == nullptr); VSIFree(ptr); #endif if (!CSLTestBoolean(CPLGetConfigOption("SKIP_MEM_INTENSIVE_TEST", "NO"))) { // The following tests will fail because such allocations cannot succeed #if SIZEOF_VOIDP == 8 ptr = static_cast( VSIMallocAligned(sizeof(void *), ~((size_t)0))); EXPECT_TRUE(ptr == nullptr); VSIFree(ptr); ptr = static_cast( VSIMallocAligned(sizeof(void *), (~((size_t)0)) - sizeof(void *))); EXPECT_TRUE(ptr == nullptr); VSIFree(ptr); #endif } } /************************************************************************/ /* CPLGetConfigOptions() / CPLSetConfigOptions() */ /************************************************************************/ TEST_F(test_cpl, CPLGetConfigOptions) { CPLSetConfigOption("FOOFOO", "BAR"); char **options = CPLGetConfigOptions(); EXPECT_STREQ(CSLFetchNameValue(options, "FOOFOO"), "BAR"); CPLSetConfigOptions(nullptr); EXPECT_STREQ(CPLGetConfigOption("FOOFOO", "i_dont_exist"), "i_dont_exist"); CPLSetConfigOptions(options); EXPECT_STREQ(CPLGetConfigOption("FOOFOO", "i_dont_exist"), "BAR"); CSLDestroy(options); } /************************************************************************/ /* CPLGetThreadLocalConfigOptions() / CPLSetThreadLocalConfigOptions() */ /************************************************************************/ TEST_F(test_cpl, CPLGetThreadLocalConfigOptions) { CPLSetThreadLocalConfigOption("FOOFOO", "BAR"); char **options = CPLGetThreadLocalConfigOptions(); EXPECT_STREQ(CSLFetchNameValue(options, "FOOFOO"), "BAR"); CPLSetThreadLocalConfigOptions(nullptr); EXPECT_STREQ(CPLGetThreadLocalConfigOption("FOOFOO", "i_dont_exist"), "i_dont_exist"); CPLSetThreadLocalConfigOptions(options); EXPECT_STREQ(CPLGetThreadLocalConfigOption("FOOFOO", "i_dont_exist"), "BAR"); CSLDestroy(options); } TEST_F(test_cpl, CPLExpandTilde) { EXPECT_STREQ(CPLExpandTilde("/foo/bar"), "/foo/bar"); CPLSetConfigOption("HOME", "/foo"); ASSERT_TRUE(EQUAL(CPLExpandTilde("~/bar"), "/foo/bar") || EQUAL(CPLExpandTilde("~/bar"), "/foo\\bar")); CPLSetConfigOption("HOME", nullptr); } TEST_F(test_cpl, CPLString_constructors) { // CPLString(std::string) constructor ASSERT_STREQ(CPLString(std::string("abc")).c_str(), "abc"); // CPLString(const char*) constructor ASSERT_STREQ(CPLString("abc").c_str(), "abc"); // CPLString(const char*, n) constructor ASSERT_STREQ(CPLString("abc", 1).c_str(), "a"); } TEST_F(test_cpl, CPLErrorSetState) { // NOTE: Assumes cpl_error.cpp defines DEFAULT_LAST_ERR_MSG_SIZE=500 char pszMsg[] = "0abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "1abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "2abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "3abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "4abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "5abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "6abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "7abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "8abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" "9abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" // 500 "0abcdefghijklmnopqrstuvwxyz0123456789!@#$%&*()_+=|" // 550 ; CPLErrorReset(); CPLErrorSetState(CE_Warning, 1, pszMsg); ASSERT_EQ(strlen(pszMsg) - 50 - 1, // length - 50 - 1 (null-terminator) strlen(CPLGetLastErrorMsg())); // DEFAULT_LAST_ERR_MSG_SIZE - 1 } TEST_F(test_cpl, CPLUnescapeString) { char *pszText = CPLUnescapeString( "<>&'"???", nullptr, CPLES_XML); EXPECT_STREQ(pszText, "<>&'\"???"); CPLFree(pszText); // Integer overflow pszText = CPLUnescapeString("&10000000000000000;", nullptr, CPLES_XML); // We do not really care about the return value CPLFree(pszText); // Integer overflow pszText = CPLUnescapeString("�", nullptr, CPLES_XML); // We do not really care about the return value CPLFree(pszText); // Error case pszText = CPLUnescapeString("&foo", nullptr, CPLES_XML); EXPECT_STREQ(pszText, ""); CPLFree(pszText); // Error case pszText = CPLUnescapeString("&#x", nullptr, CPLES_XML); EXPECT_STREQ(pszText, ""); CPLFree(pszText); // Error case pszText = CPLUnescapeString("&#", nullptr, CPLES_XML); EXPECT_STREQ(pszText, ""); CPLFree(pszText); } // Test signed int safe maths TEST_F(test_cpl, CPLSM_signed) { ASSERT_EQ((CPLSM(-2) + CPLSM(3)).v(), 1); ASSERT_EQ((CPLSM(-2) + CPLSM(1)).v(), -1); ASSERT_EQ((CPLSM(-2) + CPLSM(-1)).v(), -3); ASSERT_EQ((CPLSM(2) + CPLSM(-3)).v(), -1); ASSERT_EQ((CPLSM(2) + CPLSM(-1)).v(), 1); ASSERT_EQ((CPLSM(2) + CPLSM(1)).v(), 3); ASSERT_EQ((CPLSM(INT_MAX - 1) + CPLSM(1)).v(), INT_MAX); ASSERT_EQ((CPLSM(1) + CPLSM(INT_MAX - 1)).v(), INT_MAX); ASSERT_EQ((CPLSM(INT_MAX) + CPLSM(-1)).v(), INT_MAX - 1); ASSERT_EQ((CPLSM(-1) + CPLSM(INT_MAX)).v(), INT_MAX - 1); ASSERT_EQ((CPLSM(INT_MIN + 1) + CPLSM(-1)).v(), INT_MIN); ASSERT_EQ((CPLSM(-1) + CPLSM(INT_MIN + 1)).v(), INT_MIN); try { (CPLSM(INT_MAX) + CPLSM(1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(1) + CPLSM(INT_MAX)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(INT_MIN) + CPLSM(-1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(-1) + CPLSM(INT_MIN)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(-2) - CPLSM(1)).v(), -3); ASSERT_EQ((CPLSM(-2) - CPLSM(-1)).v(), -1); ASSERT_EQ((CPLSM(-2) - CPLSM(-3)).v(), 1); ASSERT_EQ((CPLSM(2) - CPLSM(-1)).v(), 3); ASSERT_EQ((CPLSM(2) - CPLSM(1)).v(), 1); ASSERT_EQ((CPLSM(2) - CPLSM(3)).v(), -1); ASSERT_EQ((CPLSM(INT_MAX) - CPLSM(1)).v(), INT_MAX - 1); ASSERT_EQ((CPLSM(INT_MIN + 1) - CPLSM(1)).v(), INT_MIN); ASSERT_EQ((CPLSM(0) - CPLSM(INT_MIN + 1)).v(), INT_MAX); ASSERT_EQ((CPLSM(0) - CPLSM(INT_MAX)).v(), -INT_MAX); try { (CPLSM(INT_MIN) - CPLSM(1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(0) - CPLSM(INT_MIN)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(INT_MIN) - CPLSM(1)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(INT_MIN + 1) * CPLSM(-1)).v(), INT_MAX); ASSERT_EQ((CPLSM(-1) * CPLSM(INT_MIN + 1)).v(), INT_MAX); ASSERT_EQ((CPLSM(INT_MIN) * CPLSM(1)).v(), INT_MIN); ASSERT_EQ((CPLSM(1) * CPLSM(INT_MIN)).v(), INT_MIN); ASSERT_EQ((CPLSM(1) * CPLSM(INT_MAX)).v(), INT_MAX); ASSERT_EQ((CPLSM(INT_MIN / 2) * CPLSM(2)).v(), INT_MIN); ASSERT_EQ((CPLSM(INT_MAX / 2) * CPLSM(2)).v(), INT_MAX - 1); ASSERT_EQ((CPLSM(INT_MAX / 2 + 1) * CPLSM(-2)).v(), INT_MIN); ASSERT_EQ((CPLSM(0) * CPLSM(INT_MIN)).v(), 0); ASSERT_EQ((CPLSM(INT_MIN) * CPLSM(0)).v(), 0); ASSERT_EQ((CPLSM(0) * CPLSM(INT_MAX)).v(), 0); ASSERT_EQ((CPLSM(INT_MAX) * CPLSM(0)).v(), 0); try { (CPLSM(INT_MAX / 2 + 1) * CPLSM(2)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(2) * CPLSM(INT_MAX / 2 + 1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(INT_MIN) * CPLSM(-1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(INT_MIN) * CPLSM(2)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(2) * CPLSM(INT_MIN)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(4) / CPLSM(2)).v(), 2); ASSERT_EQ((CPLSM(4) / CPLSM(-2)).v(), -2); ASSERT_EQ((CPLSM(-4) / CPLSM(2)).v(), -2); ASSERT_EQ((CPLSM(-4) / CPLSM(-2)).v(), 2); ASSERT_EQ((CPLSM(0) / CPLSM(2)).v(), 0); ASSERT_EQ((CPLSM(0) / CPLSM(-2)).v(), 0); ASSERT_EQ((CPLSM(INT_MAX) / CPLSM(1)).v(), INT_MAX); ASSERT_EQ((CPLSM(INT_MAX) / CPLSM(-1)).v(), -INT_MAX); ASSERT_EQ((CPLSM(INT_MIN) / CPLSM(1)).v(), INT_MIN); try { (CPLSM(-1) * CPLSM(INT_MIN)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(INT_MIN) / CPLSM(-1)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(1) / CPLSM(0)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ(CPLSM_TO_UNSIGNED(1).v(), 1U); try { CPLSM_TO_UNSIGNED(-1); ASSERT_TRUE(false); } catch (...) { } } // Test unsigned int safe maths TEST_F(test_cpl, CPLSM_unsigned) { ASSERT_EQ((CPLSM(2U) + CPLSM(3U)).v(), 5U); ASSERT_EQ((CPLSM(UINT_MAX - 1) + CPLSM(1U)).v(), UINT_MAX); try { (CPLSM(UINT_MAX) + CPLSM(1U)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(4U) - CPLSM(3U)).v(), 1U); ASSERT_EQ((CPLSM(4U) - CPLSM(4U)).v(), 0U); ASSERT_EQ((CPLSM(UINT_MAX) - CPLSM(1U)).v(), UINT_MAX - 1); try { (CPLSM(4U) - CPLSM(5U)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(0U) * CPLSM(UINT_MAX)).v(), 0U); ASSERT_EQ((CPLSM(UINT_MAX) * CPLSM(0U)).v(), 0U); ASSERT_EQ((CPLSM(UINT_MAX) * CPLSM(1U)).v(), UINT_MAX); ASSERT_EQ((CPLSM(1U) * CPLSM(UINT_MAX)).v(), UINT_MAX); try { (CPLSM(UINT_MAX) * CPLSM(2U)).v(); ASSERT_TRUE(false); } catch (...) { } try { (CPLSM(2U) * CPLSM(UINT_MAX)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(4U) / CPLSM(2U)).v(), 2U); ASSERT_EQ((CPLSM(UINT_MAX) / CPLSM(1U)).v(), UINT_MAX); try { (CPLSM(1U) / CPLSM(0U)).v(); ASSERT_TRUE(false); } catch (...) { } ASSERT_EQ((CPLSM(static_cast(2) * 1000 * 1000 * 1000) + CPLSM(static_cast(3) * 1000 * 1000 * 1000)) .v(), static_cast(5) * 1000 * 1000 * 1000); ASSERT_EQ((CPLSM(std::numeric_limits::max() - 1) + CPLSM(static_cast(1))) .v(), std::numeric_limits::max()); try { (CPLSM(std::numeric_limits::max()) + CPLSM(static_cast(1))); } catch (...) { } ASSERT_EQ((CPLSM(static_cast(2) * 1000 * 1000 * 1000) * CPLSM(static_cast(3) * 1000 * 1000 * 1000)) .v(), static_cast(6) * 1000 * 1000 * 1000 * 1000 * 1000 * 1000); ASSERT_EQ((CPLSM(std::numeric_limits::max()) * CPLSM(static_cast(1))) .v(), std::numeric_limits::max()); try { (CPLSM(std::numeric_limits::max()) * CPLSM(static_cast(2))); } catch (...) { } } // Test CPLParseRFC822DateTime() TEST_F(test_cpl, CPLParseRFC822DateTime) { int year, month, day, hour, min, sec, tz, weekday; ASSERT_TRUE(!CPLParseRFC822DateTime("", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_EQ(CPLParseRFC822DateTime("Thu, 15 Jan 2017 12:34:56 +0015", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr), TRUE); ASSERT_EQ(CPLParseRFC822DateTime("Thu, 15 Jan 2017 12:34:56 +0015", &year, &month, &day, &hour, &min, &sec, &tz, &weekday), TRUE); ASSERT_EQ(year, 2017); ASSERT_EQ(month, 1); ASSERT_EQ(day, 15); ASSERT_EQ(hour, 12); ASSERT_EQ(min, 34); ASSERT_EQ(sec, 56); ASSERT_EQ(tz, 101); ASSERT_EQ(weekday, 4); ASSERT_EQ(CPLParseRFC822DateTime("Thu, 15 Jan 2017 12:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday), TRUE); ASSERT_EQ(year, 2017); ASSERT_EQ(month, 1); ASSERT_EQ(day, 15); ASSERT_EQ(hour, 12); ASSERT_EQ(min, 34); ASSERT_EQ(sec, 56); ASSERT_EQ(tz, 100); ASSERT_EQ(weekday, 4); // Without day of week, second and timezone ASSERT_EQ(CPLParseRFC822DateTime("15 Jan 2017 12:34", &year, &month, &day, &hour, &min, &sec, &tz, &weekday), TRUE); ASSERT_EQ(year, 2017); ASSERT_EQ(month, 1); ASSERT_EQ(day, 15); ASSERT_EQ(hour, 12); ASSERT_EQ(min, 34); ASSERT_EQ(sec, -1); ASSERT_EQ(tz, 0); ASSERT_EQ(weekday, 0); ASSERT_EQ(CPLParseRFC822DateTime("XXX, 15 Jan 2017 12:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday), TRUE); ASSERT_EQ(weekday, 0); ASSERT_TRUE(!CPLParseRFC822DateTime("Sun, 01 Jan 2017 12", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("00 Jan 2017 12:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("32 Jan 2017 12:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 XXX 2017 12:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 -1:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 24:34:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 12:-1:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 12:60:56 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 12:34:-1 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("01 Jan 2017 12:34:61 GMT", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("15 Jan 2017 12:34:56 XXX", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("15 Jan 2017 12:34:56 +-100", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); ASSERT_TRUE(!CPLParseRFC822DateTime("15 Jan 2017 12:34:56 +9900", &year, &month, &day, &hour, &min, &sec, &tz, &weekday)); } // Test CPLCopyTree() TEST_F(test_cpl, CPLCopyTree) { CPLString osTmpPath(CPLGetDirname(CPLGenerateTempFilename(nullptr))); CPLString osSrcDir(CPLFormFilename(osTmpPath, "src_dir", nullptr)); CPLString osNewDir(CPLFormFilename(osTmpPath, "new_dir", nullptr)); ASSERT_TRUE(VSIMkdir(osSrcDir, 0755) == 0); CPLString osSrcFile(CPLFormFilename(osSrcDir, "my.bin", nullptr)); VSILFILE *fp = VSIFOpenL(osSrcFile, "wb"); ASSERT_TRUE(fp != nullptr); VSIFCloseL(fp); CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(CPLCopyTree(osNewDir, "/i/do_not/exist") < 0); CPLPopErrorHandler(); ASSERT_TRUE(CPLCopyTree(osNewDir, osSrcDir) == 0); VSIStatBufL sStat; CPLString osNewFile(CPLFormFilename(osNewDir, "my.bin", nullptr)); ASSERT_TRUE(VSIStatL(osNewFile, &sStat) == 0); CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(CPLCopyTree(osNewDir, osSrcDir) < 0); CPLPopErrorHandler(); VSIUnlink(osNewFile); VSIRmdir(osNewDir); VSIUnlink(osSrcFile); VSIRmdir(osSrcDir); } class CPLJSonStreamingParserDump : public CPLJSonStreamingParser { std::vector m_abFirstMember; CPLString m_osSerialized; CPLString m_osException; public: CPLJSonStreamingParserDump() { } virtual void Reset() CPL_OVERRIDE { m_osSerialized.clear(); m_osException.clear(); CPLJSonStreamingParser::Reset(); } virtual void String(const char *pszValue, size_t) CPL_OVERRIDE; virtual void Number(const char *pszValue, size_t) CPL_OVERRIDE; virtual void Boolean(bool bVal) CPL_OVERRIDE; virtual void Null() CPL_OVERRIDE; virtual void StartObject() CPL_OVERRIDE; virtual void EndObject() CPL_OVERRIDE; virtual void StartObjectMember(const char *pszKey, size_t) CPL_OVERRIDE; virtual void StartArray() CPL_OVERRIDE; virtual void EndArray() CPL_OVERRIDE; virtual void StartArrayMember() CPL_OVERRIDE; virtual void Exception(const char *pszMessage) CPL_OVERRIDE; const CPLString &GetSerialized() const { return m_osSerialized; } const CPLString &GetException() const { return m_osException; } }; void CPLJSonStreamingParserDump::StartObject() { m_osSerialized += "{"; m_abFirstMember.push_back(true); } void CPLJSonStreamingParserDump::EndObject() { m_osSerialized += "}"; m_abFirstMember.pop_back(); } void CPLJSonStreamingParserDump::StartObjectMember(const char *pszKey, size_t) { if (!m_abFirstMember.back()) m_osSerialized += ", "; m_osSerialized += CPLSPrintf("\"%s\": ", pszKey); m_abFirstMember.back() = false; } void CPLJSonStreamingParserDump::String(const char *pszValue, size_t) { m_osSerialized += GetSerializedString(pszValue); } void CPLJSonStreamingParserDump::Number(const char *pszValue, size_t) { m_osSerialized += pszValue; } void CPLJSonStreamingParserDump::Boolean(bool bVal) { m_osSerialized += bVal ? "true" : "false"; } void CPLJSonStreamingParserDump::Null() { m_osSerialized += "null"; } void CPLJSonStreamingParserDump::StartArray() { m_osSerialized += "["; m_abFirstMember.push_back(true); } void CPLJSonStreamingParserDump::EndArray() { m_osSerialized += "]"; m_abFirstMember.pop_back(); } void CPLJSonStreamingParserDump::StartArrayMember() { if (!m_abFirstMember.back()) m_osSerialized += ", "; m_abFirstMember.back() = false; } void CPLJSonStreamingParserDump::Exception(const char *pszMessage) { m_osException = pszMessage; } // Test CPLJSonStreamingParser() TEST_F(test_cpl, CPLJSonStreamingParser) { // nominal cases { CPLJSonStreamingParserDump oParser; const char sText[] = "true"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "false"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "null"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "10"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "123eE-34"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\\\a\\b\\f\\n\\r\\t\\u0020\\u0001\\\"\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "\"\\\\a\\b\\f\\n\\r\\t \\u0001\\\"\""); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), "\"\\\\a\\b\\f\\n\\r\\t \\u0001\\\"\""); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\u0001\\u0020\\ud834\\uDD1E\\uDD1E\\uD834\\uD834\\uD834\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ( oParser.GetSerialized(), "\"\\u0001 \xf0\x9d\x84\x9e\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\""); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\ud834\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "\"\xef\xbf\xbd\""); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\ud834\\t\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "\"\xef\xbf\xbd\\t\""); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\u00e9\""; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "\"\xc3\xa9\""); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{}"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[]"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[[]]"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[1]"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[1,2]"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "[1, 2]"); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), "[1, 2]"); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{\"a\":null}"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), "{\"a\": null}"); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), "{\"a\": null}"); } { CPLJSonStreamingParserDump oParser; const char sText[] = " { \"a\" : null ,\r\n\t\"b\": {\"c\": 1}, \"d\": [1] }"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); const char sExpected[] = "{\"a\": null, \"b\": {\"c\": 1}, \"d\": [1]}"; ASSERT_EQ(oParser.GetSerialized(), sExpected); oParser.Reset(); ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sExpected); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sExpected); } { CPLJSonStreamingParserDump oParser; const char sText[] = "infinity"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "-infinity"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } { CPLJSonStreamingParserDump oParser; const char sText[] = "nan"; ASSERT_TRUE(oParser.Parse(sText, strlen(sText), true)); ASSERT_EQ(oParser.GetSerialized(), sText); oParser.Reset(); for (size_t i = 0; sText[i]; i++) ASSERT_TRUE(oParser.Parse(sText + i, 1, sText[i + 1] == 0)); ASSERT_EQ(oParser.GetSerialized(), sText); } // errors { CPLJSonStreamingParserDump oParser; const char sText[] = "tru"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "tru1"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "truxe"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "truex"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "fals"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "falsxe"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "falsex"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "nul"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "nulxl"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "nullx"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "na"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "nanx"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "infinit"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "infinityx"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "-infinit"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "-infinityx"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "true false"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "x"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "}"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "["; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[1"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[,"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[|"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "]"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ :"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ ,"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ |"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ 1"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\""; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\": "; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\": 1 2"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\", "; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\" }"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{\"a\" x}"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "1x"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\""; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\x\""; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\u"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\ux"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\u000"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\uD834\\ux\""; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"\\\""; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "\"too long\""; oParser.SetMaxStringSize(2); ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[[]]"; oParser.SetMaxDepth(1); ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "{ \"x\": {} }"; oParser.SetMaxDepth(1); ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[,]"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[true,]"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[true,,true]"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } { CPLJSonStreamingParserDump oParser; const char sText[] = "[true true]"; ASSERT_TRUE(!oParser.Parse(sText, strlen(sText), true)); ASSERT_TRUE(!oParser.GetException().empty()); } } // Test cpl_mem_cache TEST_F(test_cpl, cpl_mem_cache) { lru11::Cache cache(2, 1); ASSERT_EQ(cache.size(), 0U); ASSERT_TRUE(cache.empty()); cache.clear(); int val; ASSERT_TRUE(!cache.tryGet(0, val)); try { cache.get(0); ASSERT_TRUE(false); } catch (const lru11::KeyNotFound &) { ASSERT_TRUE(true); } ASSERT_TRUE(!cache.remove(0)); ASSERT_TRUE(!cache.contains(0)); ASSERT_EQ(cache.getMaxSize(), 2U); ASSERT_EQ(cache.getElasticity(), 1U); ASSERT_EQ(cache.getMaxAllowedSize(), 3U); int out; ASSERT_TRUE(!cache.removeAndRecycleOldestEntry(out)); cache.insert(0, 1); val = 0; ASSERT_TRUE(cache.tryGet(0, val)); int *ptr = cache.getPtr(0); ASSERT_TRUE(ptr); ASSERT_EQ(*ptr, 1); ASSERT_TRUE(cache.getPtr(-1) == nullptr); ASSERT_EQ(val, 1); ASSERT_EQ(cache.get(0), 1); ASSERT_EQ(cache.getCopy(0), 1); ASSERT_EQ(cache.size(), 1U); ASSERT_TRUE(!cache.empty()); ASSERT_TRUE(cache.contains(0)); bool visited = false; auto lambda = [&visited](const lru11::KeyValuePair &kv) { if (kv.key == 0 && kv.value == 1) visited = true; }; cache.cwalk(lambda); ASSERT_TRUE(visited); out = -1; ASSERT_TRUE(cache.removeAndRecycleOldestEntry(out)); ASSERT_EQ(out, 1); cache.insert(0, 1); cache.insert(0, 2); ASSERT_EQ(cache.get(0), 2); ASSERT_EQ(cache.size(), 1U); cache.insert(1, 3); cache.insert(2, 4); ASSERT_EQ(cache.size(), 3U); cache.insert(3, 5); ASSERT_EQ(cache.size(), 2U); ASSERT_TRUE(cache.contains(2)); ASSERT_TRUE(cache.contains(3)); ASSERT_TRUE(!cache.contains(0)); ASSERT_TRUE(!cache.contains(1)); ASSERT_TRUE(cache.remove(2)); ASSERT_TRUE(!cache.contains(2)); ASSERT_EQ(cache.size(), 1U); { // Check that MyObj copy constructor and copy-assignment operator // are not needed struct MyObj { int m_v; MyObj(int v) : m_v(v) { } MyObj(const MyObj &) = delete; MyObj &operator=(const MyObj &) = delete; MyObj(MyObj &&) = default; MyObj &operator=(MyObj &&) = default; }; lru11::Cache cacheMyObj(2, 0); ASSERT_EQ(cacheMyObj.insert(0, MyObj(0)).m_v, 0); cacheMyObj.getPtr(0); ASSERT_EQ(cacheMyObj.insert(1, MyObj(1)).m_v, 1); ASSERT_EQ(cacheMyObj.insert(2, MyObj(2)).m_v, 2); MyObj outObj(-1); cacheMyObj.removeAndRecycleOldestEntry(outObj); } { // Check that MyObj copy constructor and copy-assignment operator // are not triggered struct MyObj { int m_v; MyObj(int v) : m_v(v) { } static void should_not_happen() { ASSERT_TRUE(false); } MyObj(const MyObj &) : m_v(-1) { should_not_happen(); } MyObj &operator=(const MyObj &) { should_not_happen(); return *this; } MyObj(MyObj &&) = default; MyObj &operator=(MyObj &&) = default; }; lru11::Cache cacheMyObj(2, 0); ASSERT_EQ(cacheMyObj.insert(0, MyObj(0)).m_v, 0); cacheMyObj.getPtr(0); ASSERT_EQ(cacheMyObj.insert(1, MyObj(1)).m_v, 1); ASSERT_EQ(cacheMyObj.insert(2, MyObj(2)).m_v, 2); MyObj outObj(-1); cacheMyObj.removeAndRecycleOldestEntry(outObj); } } // Test CPLJSONDocument TEST_F(test_cpl, CPLJSONDocument) { { // Test Json document LoadUrl CPLJSONDocument oDocument; const char *options[5] = {"CONNECTTIMEOUT=15", "TIMEOUT=20", "MAX_RETRY=5", "RETRY_DELAY=1", nullptr}; oDocument.GetRoot().Add("foo", "bar"); if (CPLHTTPEnabled()) { CPLSetConfigOption("CPL_CURL_ENABLE_VSIMEM", "YES"); VSILFILE *fpTmp = VSIFOpenL("/vsimem/test.json", "wb"); const char *pszContent = "{ \"foo\": \"bar\" }"; VSIFWriteL(pszContent, 1, strlen(pszContent), fpTmp); VSIFCloseL(fpTmp); ASSERT_TRUE(oDocument.LoadUrl("/vsimem/test.json", const_cast(options))); CPLSetConfigOption("CPL_CURL_ENABLE_VSIMEM", nullptr); VSIUnlink("/vsimem/test.json"); CPLJSONObject oJsonRoot = oDocument.GetRoot(); ASSERT_TRUE(oJsonRoot.IsValid()); CPLString value = oJsonRoot.GetString("foo", ""); ASSERT_STRNE(value, "bar"); // not equal } } { // Test Json document LoadChunks CPLJSONDocument oDocument; CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(!oDocument.LoadChunks("/i_do/not/exist", 512)); CPLPopErrorHandler(); CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(!oDocument.LoadChunks("test_cpl.cpp", 512)); CPLPopErrorHandler(); oDocument.GetRoot().Add("foo", "bar"); ASSERT_TRUE( oDocument.LoadChunks((data_ + SEP + "test.json").c_str(), 512)); CPLJSONObject oJsonRoot = oDocument.GetRoot(); ASSERT_TRUE(oJsonRoot.IsValid()); ASSERT_EQ(oJsonRoot.GetInteger("resource/id", 10), 0); CPLJSONObject oJsonResource = oJsonRoot.GetObj("resource"); ASSERT_TRUE(oJsonResource.IsValid()); std::vector children = oJsonResource.GetChildren(); ASSERT_TRUE(children.size() == 11); CPLJSONArray oaScopes = oJsonRoot.GetArray("resource/scopes"); ASSERT_TRUE(oaScopes.IsValid()); ASSERT_EQ(oaScopes.Size(), 2); CPLJSONObject oHasChildren = oJsonRoot.GetObj("resource/children"); ASSERT_TRUE(oHasChildren.IsValid()); ASSERT_EQ(oHasChildren.ToBool(), true); ASSERT_EQ(oJsonResource.GetBool("children", false), true); CPLJSONObject oJsonId = oJsonRoot["resource/owner_user/id"]; ASSERT_TRUE(oJsonId.IsValid()); } { CPLJSONDocument oDocument; ASSERT_TRUE(!oDocument.LoadMemory(nullptr, 0)); ASSERT_TRUE(!oDocument.LoadMemory(CPLString())); ASSERT_TRUE(oDocument.LoadMemory(std::string("true"))); ASSERT_TRUE(oDocument.GetRoot().GetType() == CPLJSONObject::Type::Boolean); ASSERT_TRUE(oDocument.GetRoot().ToBool()); ASSERT_TRUE(oDocument.LoadMemory(std::string("false"))); ASSERT_TRUE(oDocument.GetRoot().GetType() == CPLJSONObject::Type::Boolean); ASSERT_TRUE(!oDocument.GetRoot().ToBool()); } { // Copy constructor CPLJSONDocument oDocument; ASSERT_TRUE(oDocument.LoadMemory(std::string("true"))); oDocument.GetRoot(); CPLJSONDocument oDocument2(oDocument); CPLJSONObject oObj(oDocument.GetRoot()); ASSERT_TRUE(oObj.ToBool()); CPLJSONObject oObj2(oObj); ASSERT_TRUE(oObj2.ToBool()); // Assignment operator oDocument2 = oDocument; auto &oDocument2Ref(oDocument2); oDocument2 = oDocument2Ref; oObj2 = oObj; auto &oObj2Ref(oObj2); oObj2 = oObj2Ref; CPLJSONObject oObj3(std::move(oObj2)); ASSERT_TRUE(oObj3.ToBool()); CPLJSONObject oObj4; oObj4 = std::move(oObj3); ASSERT_TRUE(oObj4.ToBool()); } { // Move constructor CPLJSONDocument oDocument; oDocument.GetRoot(); CPLJSONDocument oDocument2(std::move(oDocument)); } { // Move assignment CPLJSONDocument oDocument; oDocument.GetRoot(); CPLJSONDocument oDocument2; oDocument2 = std::move(oDocument); } { // Save CPLJSONDocument oDocument; CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(!oDocument.Save("/i_do/not/exist")); CPLPopErrorHandler(); } { CPLJSONObject oObj; oObj.Add("string", std::string("my_string")); ASSERT_EQ(oObj.GetString("string"), std::string("my_string")); ASSERT_EQ(oObj.GetString("inexisting_string", "default"), std::string("default")); oObj.Add("const_char_star", nullptr); oObj.Add("const_char_star", "my_const_char_star"); ASSERT_TRUE(oObj.GetObj("const_char_star").GetType() == CPLJSONObject::Type::String); oObj.Add("int", 1); ASSERT_EQ(oObj.GetInteger("int"), 1); ASSERT_EQ(oObj.GetInteger("inexisting_int", -987), -987); ASSERT_TRUE(oObj.GetObj("int").GetType() == CPLJSONObject::Type::Integer); oObj.Add("int64", GINT64_MAX); ASSERT_EQ(oObj.GetLong("int64"), GINT64_MAX); ASSERT_EQ(oObj.GetLong("inexisting_int64", GINT64_MIN), GINT64_MIN); ASSERT_TRUE(oObj.GetObj("int64").GetType() == CPLJSONObject::Type::Long); oObj.Add("double", 1.25); ASSERT_EQ(oObj.GetDouble("double"), 1.25); ASSERT_EQ(oObj.GetDouble("inexisting_double", -987.0), -987.0); ASSERT_TRUE(oObj.GetObj("double").GetType() == CPLJSONObject::Type::Double); oObj.Add("array", CPLJSONArray()); ASSERT_TRUE(oObj.GetObj("array").GetType() == CPLJSONObject::Type::Array); oObj.Add("obj", CPLJSONObject()); ASSERT_TRUE(oObj.GetObj("obj").GetType() == CPLJSONObject::Type::Object); oObj.Add("bool", true); ASSERT_EQ(oObj.GetBool("bool"), true); ASSERT_EQ(oObj.GetBool("inexisting_bool", false), false); ASSERT_TRUE(oObj.GetObj("bool").GetType() == CPLJSONObject::Type::Boolean); oObj.AddNull("null_field"); ASSERT_TRUE(oObj.GetObj("null_field").GetType() == CPLJSONObject::Type::Null); ASSERT_TRUE(oObj.GetObj("inexisting").GetType() == CPLJSONObject::Type::Unknown); oObj.Set("string", std::string("my_string")); oObj.Set("const_char_star", nullptr); oObj.Set("const_char_star", "my_const_char_star"); oObj.Set("int", 1); oObj.Set("int64", GINT64_MAX); oObj.Set("double", 1.25); // oObj.Set("array", CPLJSONArray()); // oObj.Set("obj", CPLJSONObject()); oObj.Set("bool", true); oObj.SetNull("null_field"); ASSERT_TRUE(CPLJSONArray().GetChildren().empty()); oObj.ToArray(); ASSERT_EQ(CPLJSONObject().Format(CPLJSONObject::PrettyFormat::Spaced), std::string("{ }")); ASSERT_EQ(CPLJSONObject().Format(CPLJSONObject::PrettyFormat::Pretty), std::string("{\n}")); ASSERT_EQ(CPLJSONObject().Format(CPLJSONObject::PrettyFormat::Plain), std::string("{}")); } { CPLJSONArray oArrayConstructorString(std::string("foo")); CPLJSONArray oArray; oArray.Add(CPLJSONObject()); oArray.Add(std::string("str")); oArray.Add("const_char_star"); oArray.Add(1.25); oArray.Add(1); oArray.Add(GINT64_MAX); oArray.Add(true); ASSERT_EQ(oArray.Size(), 7); int nCount = 0; for (const auto &obj : oArray) { ASSERT_EQ(obj.GetInternalHandle(), oArray[nCount].GetInternalHandle()); nCount++; } ASSERT_EQ(nCount, 7); } { CPLJSONDocument oDocument; ASSERT_TRUE(oDocument.LoadMemory(CPLString("{ \"/foo\" : \"bar\" }"))); ASSERT_EQ(oDocument.GetRoot().GetString("/foo"), std::string("bar")); } } // Test CPLRecodeIconv() with re-allocation TEST_F(test_cpl, CPLRecodeIconv) { #ifdef CPL_RECODE_ICONV int N = 32800; char *pszIn = static_cast(CPLMalloc(N + 1)); for (int i = 0; i < N; i++) pszIn[i] = '\xE9'; pszIn[N] = 0; char *pszExpected = static_cast(CPLMalloc(N * 2 + 1)); for (int i = 0; i < N; i++) { pszExpected[2 * i] = '\xC3'; pszExpected[2 * i + 1] = '\xA9'; } pszExpected[N * 2] = 0; char *pszRet = CPLRecode(pszIn, "ISO-8859-2", CPL_ENC_UTF8); EXPECT_EQ(memcmp(pszExpected, pszRet, N * 2 + 1), 0); CPLFree(pszIn); CPLFree(pszRet); CPLFree(pszExpected); #else GTEST_SKIP() << "CPL_RECODE_ICONV missing"; #endif } // Test CPLHTTPParseMultipartMime() TEST_F(test_cpl, CPLHTTPParseMultipartMime) { CPLHTTPResult *psResult; psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Missing boundary value psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary="); CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // No content psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // No part psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Missing end boundary psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "\r\n" "Bla"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Truncated header psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "Content-Type: foo"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Invalid end boundary psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "\r\n" "Bla" "\r\n" "--myboundary"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Invalid end boundary psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "\r\n" "Bla" "\r\n" "--myboundary"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLPopErrorHandler(); CPLHTTPDestroyResult(psResult); // Valid single part, no header psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "\r\n" "Bla" "\r\n" "--myboundary--\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); EXPECT_EQ(psResult->nMimePartCount, 1); if (psResult->nMimePartCount == 1) { EXPECT_EQ(psResult->pasMimePart[0].papszHeaders, static_cast(nullptr)); EXPECT_EQ(psResult->pasMimePart[0].nDataLen, 3); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[0].pabyData), "Bla", 3) == 0); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLHTTPDestroyResult(psResult); // Valid single part, with header psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "Content-Type: bla\r\n" "\r\n" "Bla" "\r\n" "--myboundary--\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); EXPECT_EQ(psResult->nMimePartCount, 1); if (psResult->nMimePartCount == 1) { EXPECT_EQ(CSLCount(psResult->pasMimePart[0].papszHeaders), 1); EXPECT_STREQ(psResult->pasMimePart[0].papszHeaders[0], "Content-Type=bla"); EXPECT_EQ(psResult->pasMimePart[0].nDataLen, 3); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[0].pabyData), "Bla", 3) == 0); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLHTTPDestroyResult(psResult); // Valid single part, 2 headers psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "Content-Type: bla\r\n" "Content-Disposition: bar\r\n" "\r\n" "Bla" "\r\n" "--myboundary--\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); EXPECT_EQ(psResult->nMimePartCount, 1); if (psResult->nMimePartCount == 1) { EXPECT_EQ(CSLCount(psResult->pasMimePart[0].papszHeaders), 2); EXPECT_STREQ(psResult->pasMimePart[0].papszHeaders[0], "Content-Type=bla"); EXPECT_STREQ(psResult->pasMimePart[0].papszHeaders[1], "Content-Disposition=bar"); EXPECT_EQ(psResult->pasMimePart[0].nDataLen, 3); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[0].pabyData), "Bla", 3) == 0); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLHTTPDestroyResult(psResult); // Single part, but with header without extra terminating \r\n // (invalid normally, but apparently necessary for some ArcGIS WCS // implementations) psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "Content-Type: bla\r\n" "Bla" "\r\n" "--myboundary--\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); EXPECT_EQ(psResult->nMimePartCount, 1); if (psResult->nMimePartCount == 1) { EXPECT_STREQ(psResult->pasMimePart[0].papszHeaders[0], "Content-Type=bla"); EXPECT_EQ(psResult->pasMimePart[0].nDataLen, 3); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[0].pabyData), "Bla", 3) == 0); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLHTTPDestroyResult(psResult); // Valid 2 parts, no header psResult = static_cast(CPLCalloc(1, sizeof(CPLHTTPResult))); psResult->pszContentType = CPLStrdup("multipart/form-data; boundary=myboundary"); { const char *pszText = "--myboundary some junk\r\n" "\r\n" "Bla" "\r\n" "--myboundary\r\n" "\r\n" "second part" "\r\n" "--myboundary--\r\n"; psResult->pabyData = reinterpret_cast(CPLStrdup(pszText)); psResult->nDataLen = static_cast(strlen(pszText)); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); EXPECT_EQ(psResult->nMimePartCount, 2); if (psResult->nMimePartCount == 2) { EXPECT_EQ(psResult->pasMimePart[0].papszHeaders, static_cast(nullptr)); EXPECT_EQ(psResult->pasMimePart[0].nDataLen, 3); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[0].pabyData), "Bla", 3) == 0); EXPECT_EQ(psResult->pasMimePart[1].nDataLen, 11); EXPECT_TRUE( strncmp(reinterpret_cast(psResult->pasMimePart[1].pabyData), "second part", 11) == 0); } EXPECT_TRUE(CPL_TO_BOOL(CPLHTTPParseMultipartMime(psResult))); CPLHTTPDestroyResult(psResult); } // Test cpl::down_cast TEST_F(test_cpl, down_cast) { struct Base { virtual ~Base() { } }; struct Derived : public Base { }; Base b; Derived d; Base *p_b_d = &d; #ifdef wont_compile struct OtherBase { }; OtherBase ob; ASSERT_EQ(cpl::down_cast(p_b_d), &ob); #endif #ifdef compile_with_warning ASSERT_EQ(cpl::down_cast(p_b_d), p_b_d); #endif ASSERT_EQ(cpl::down_cast(p_b_d), &d); ASSERT_EQ(cpl::down_cast(static_cast(nullptr)), static_cast(nullptr)); } // Test CPLPrintTime() in particular case of RFC822 formatting in C locale TEST_F(test_cpl, CPLPrintTime_RFC822) { char szDate[64]; struct tm tm; tm.tm_sec = 56; tm.tm_min = 34; tm.tm_hour = 12; tm.tm_mday = 20; tm.tm_mon = 6 - 1; tm.tm_year = 2018 - 1900; tm.tm_wday = 3; // Wednesday tm.tm_yday = 0; // unused tm.tm_isdst = 0; // unused int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1, "%a, %d %b %Y %H:%M:%S GMT", &tm, "C"); szDate[nRet] = 0; ASSERT_STREQ(szDate, "Wed, 20 Jun 2018 12:34:56 GMT"); } // Test CPLAutoClose TEST_F(test_cpl, CPLAutoClose) { static int counter = 0; class AutoCloseTest { public: AutoCloseTest() { counter += 222; } virtual ~AutoCloseTest() { counter -= 22; } static AutoCloseTest *Create() { return new AutoCloseTest; } static void Destroy(AutoCloseTest *p) { delete p; } }; { AutoCloseTest *p1 = AutoCloseTest::Create(); CPL_AUTO_CLOSE_WARP(p1, AutoCloseTest::Destroy); AutoCloseTest *p2 = AutoCloseTest::Create(); CPL_AUTO_CLOSE_WARP(p2, AutoCloseTest::Destroy); } ASSERT_EQ(counter, 400); } // Test cpl_minixml TEST_F(test_cpl, cpl_minixml) { CPLXMLNode *psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "Root"); CPLXMLNode *psElt = CPLCreateXMLElementAndValue(psRoot, "Elt", "value"); CPLAddXMLAttributeAndValue(psElt, "attr1", "val1"); CPLAddXMLAttributeAndValue(psElt, "attr2", "val2"); char *str = CPLSerializeXMLTree(psRoot); CPLDestroyXMLNode(psRoot); ASSERT_STREQ( str, "\n value\n\n"); CPLFree(str); } // Test CPLCharUniquePtr TEST_F(test_cpl, CPLCharUniquePtr) { CPLCharUniquePtr x; ASSERT_TRUE(x.get() == nullptr); x.reset(CPLStrdup("foo")); ASSERT_STREQ(x.get(), "foo"); } // Test CPLJSonStreamingWriter TEST_F(test_cpl, CPLJSonStreamingWriter) { { CPLJSonStreamingWriter x(nullptr, nullptr); ASSERT_EQ(x.GetString(), std::string()); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(true); ASSERT_EQ(x.GetString(), std::string("true")); } { std::string res; struct MyCallback { static void f(const char *pszText, void *user_data) { *static_cast(user_data) += pszText; } }; CPLJSonStreamingWriter x(&MyCallback::f, &res); x.Add(true); ASSERT_EQ(x.GetString(), std::string()); ASSERT_EQ(res, std::string("true")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(false); ASSERT_EQ(x.GetString(), std::string("false")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.AddNull(); ASSERT_EQ(x.GetString(), std::string("null")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(1); ASSERT_EQ(x.GetString(), std::string("1")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(4200000000U); ASSERT_EQ(x.GetString(), std::string("4200000000")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(static_cast(-10000) * 1000000); ASSERT_EQ(x.GetString(), std::string("-10000000000")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(static_cast(10000) * 1000000); ASSERT_EQ(x.GetString(), std::string("10000000000")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(1.5f); ASSERT_EQ(x.GetString(), std::string("1.5")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(std::numeric_limits::quiet_NaN()); ASSERT_EQ(x.GetString(), std::string("\"NaN\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(std::numeric_limits::infinity()); ASSERT_EQ(x.GetString(), std::string("\"Infinity\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(-std::numeric_limits::infinity()); ASSERT_EQ(x.GetString(), std::string("\"-Infinity\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(1.25); ASSERT_EQ(x.GetString(), std::string("1.25")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(std::numeric_limits::quiet_NaN()); ASSERT_EQ(x.GetString(), std::string("\"NaN\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(std::numeric_limits::infinity()); ASSERT_EQ(x.GetString(), std::string("\"Infinity\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(-std::numeric_limits::infinity()); ASSERT_EQ(x.GetString(), std::string("\"-Infinity\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add(std::string("foo\\bar\"baz\b\f\n\r\t" "\x01" "boo")); ASSERT_EQ( x.GetString(), std::string("\"foo\\\\bar\\\"baz\\b\\f\\n\\r\\t\\u0001boo\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.Add("foo\\bar\"baz\b\f\n\r\t" "\x01" "boo"); ASSERT_EQ( x.GetString(), std::string("\"foo\\\\bar\\\"baz\\b\\f\\n\\r\\t\\u0001boo\"")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.SetPrettyFormatting(false); { auto ctxt(x.MakeObjectContext()); } ASSERT_EQ(x.GetString(), std::string("{}")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeObjectContext()); } ASSERT_EQ(x.GetString(), std::string("{}")); } { CPLJSonStreamingWriter x(nullptr, nullptr); x.SetPrettyFormatting(false); { auto ctxt(x.MakeObjectContext()); x.AddObjKey("key"); x.Add("value"); } ASSERT_EQ(x.GetString(), std::string("{\"key\":\"value\"}")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeObjectContext()); x.AddObjKey("key"); x.Add("value"); } ASSERT_EQ(x.GetString(), std::string("{\n \"key\": \"value\"\n}")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeObjectContext()); x.AddObjKey("key"); x.Add("value"); x.AddObjKey("key2"); x.Add("value2"); } ASSERT_EQ( x.GetString(), std::string("{\n \"key\": \"value\",\n \"key2\": \"value2\"\n}")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeArrayContext()); } ASSERT_EQ(x.GetString(), std::string("[]")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeArrayContext()); x.Add(1); } ASSERT_EQ(x.GetString(), std::string("[\n 1\n]")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeArrayContext()); x.Add(1); x.Add(2); } ASSERT_EQ(x.GetString(), std::string("[\n 1,\n 2\n]")); } { CPLJSonStreamingWriter x(nullptr, nullptr); { auto ctxt(x.MakeArrayContext(true)); x.Add(1); x.Add(2); } ASSERT_EQ(x.GetString(), std::string("[1, 2]")); } } // Test CPLWorkerThreadPool TEST_F(test_cpl, CPLWorkerThreadPool) { CPLWorkerThreadPool oPool; ASSERT_TRUE(oPool.Setup(2, nullptr, nullptr, false)); const auto myJob = [](void *pData) { (*static_cast(pData))++; }; { std::vector res(1000); for (int i = 0; i < 1000; i++) { res[i] = i; oPool.SubmitJob(myJob, &res[i]); } oPool.WaitCompletion(); for (int i = 0; i < 1000; i++) { ASSERT_EQ(res[i], i + 1); } } { std::vector res(1000); std::vector resPtr(1000); for (int i = 0; i < 1000; i++) { res[i] = i; resPtr[i] = res.data() + i; } oPool.SubmitJobs(myJob, resPtr); oPool.WaitEvent(); oPool.WaitCompletion(); for (int i = 0; i < 1000; i++) { ASSERT_EQ(res[i], i + 1); } } { auto jobQueue1 = oPool.CreateJobQueue(); auto jobQueue2 = oPool.CreateJobQueue(); ASSERT_EQ(jobQueue1->GetPool(), &oPool); std::vector res(1000); for (int i = 0; i < 1000; i++) { res[i] = i; if (i % 2) jobQueue1->SubmitJob(myJob, &res[i]); else jobQueue2->SubmitJob(myJob, &res[i]); } jobQueue1->WaitCompletion(); jobQueue2->WaitCompletion(); for (int i = 0; i < 1000; i++) { ASSERT_EQ(res[i], i + 1); } } } // Test CPLHTTPFetch TEST_F(test_cpl, CPLHTTPFetch) { #ifdef HAVE_CURL CPLStringList oOptions; oOptions.AddNameValue("FORM_ITEM_COUNT", "5"); oOptions.AddNameValue("FORM_KEY_0", "qqq"); oOptions.AddNameValue("FORM_VALUE_0", "www"); CPLHTTPResult *pResult = CPLHTTPFetch("http://example.com", oOptions); EXPECT_EQ(pResult->nStatus, 34); CPLHTTPDestroyResult(pResult); pResult = nullptr; oOptions.Clear(); oOptions.AddNameValue("FORM_FILE_PATH", "not_existed"); pResult = CPLHTTPFetch("http://example.com", oOptions); EXPECT_EQ(pResult->nStatus, 34); CPLHTTPDestroyResult(pResult); #else GTEST_SKIP() << "CURL not available"; #endif // HAVE_CURL } // Test CPLHTTPPushFetchCallback TEST_F(test_cpl, CPLHTTPPushFetchCallback) { struct myCbkUserDataStruct { CPLString osURL{}; CSLConstList papszOptions = nullptr; GDALProgressFunc pfnProgress = nullptr; void *pProgressArg = nullptr; CPLHTTPFetchWriteFunc pfnWrite = nullptr; void *pWriteArg = nullptr; }; const auto myCbk = [](const char *pszURL, CSLConstList papszOptions, GDALProgressFunc pfnProgress, void *pProgressArg, CPLHTTPFetchWriteFunc pfnWrite, void *pWriteArg, void *pUserData) { myCbkUserDataStruct *pCbkUserData = static_cast(pUserData); pCbkUserData->osURL = pszURL; pCbkUserData->papszOptions = papszOptions; pCbkUserData->pfnProgress = pfnProgress; pCbkUserData->pProgressArg = pProgressArg; pCbkUserData->pfnWrite = pfnWrite; pCbkUserData->pWriteArg = pWriteArg; auto psResult = static_cast(CPLCalloc(sizeof(CPLHTTPResult), 1)); psResult->nStatus = 123; return psResult; }; myCbkUserDataStruct userData; EXPECT_TRUE(CPLHTTPPushFetchCallback(myCbk, &userData)); int progressArg = 0; const auto myWriteCbk = [](void *, size_t, size_t, void *) -> size_t { return 0; }; int writeCbkArg = 00; CPLStringList aosOptions; GDALProgressFunc pfnProgress = GDALTermProgress; CPLHTTPFetchWriteFunc pfnWriteCbk = myWriteCbk; CPLHTTPResult *pResult = CPLHTTPFetchEx("http://example.com", aosOptions.List(), pfnProgress, &progressArg, pfnWriteCbk, &writeCbkArg); ASSERT_TRUE(pResult != nullptr); EXPECT_EQ(pResult->nStatus, 123); CPLHTTPDestroyResult(pResult); EXPECT_TRUE(CPLHTTPPopFetchCallback()); CPLPushErrorHandler(CPLQuietErrorHandler); EXPECT_TRUE(!CPLHTTPPopFetchCallback()); CPLPopErrorHandler(); EXPECT_STREQ(userData.osURL, "http://example.com"); EXPECT_EQ(userData.papszOptions, aosOptions.List()); EXPECT_EQ(userData.pfnProgress, pfnProgress); EXPECT_EQ(userData.pProgressArg, &progressArg); EXPECT_EQ(userData.pfnWrite, pfnWriteCbk); EXPECT_EQ(userData.pWriteArg, &writeCbkArg); } // Test CPLHTTPSetFetchCallback TEST_F(test_cpl, CPLHTTPSetFetchCallback) { struct myCbkUserDataStruct { CPLString osURL{}; CSLConstList papszOptions = nullptr; GDALProgressFunc pfnProgress = nullptr; void *pProgressArg = nullptr; CPLHTTPFetchWriteFunc pfnWrite = nullptr; void *pWriteArg = nullptr; }; const auto myCbk2 = [](const char *pszURL, CSLConstList papszOptions, GDALProgressFunc pfnProgress, void *pProgressArg, CPLHTTPFetchWriteFunc pfnWrite, void *pWriteArg, void *pUserData) { myCbkUserDataStruct *pCbkUserData = static_cast(pUserData); pCbkUserData->osURL = pszURL; pCbkUserData->papszOptions = papszOptions; pCbkUserData->pfnProgress = pfnProgress; pCbkUserData->pProgressArg = pProgressArg; pCbkUserData->pfnWrite = pfnWrite; pCbkUserData->pWriteArg = pWriteArg; auto psResult = static_cast(CPLCalloc(sizeof(CPLHTTPResult), 1)); psResult->nStatus = 124; return psResult; }; myCbkUserDataStruct userData2; CPLHTTPSetFetchCallback(myCbk2, &userData2); int progressArg = 0; const auto myWriteCbk = [](void *, size_t, size_t, void *) -> size_t { return 0; }; int writeCbkArg = 00; CPLStringList aosOptions; GDALProgressFunc pfnProgress = GDALTermProgress; CPLHTTPFetchWriteFunc pfnWriteCbk = myWriteCbk; CPLHTTPResult *pResult = CPLHTTPFetchEx("http://example.com", aosOptions.List(), pfnProgress, &progressArg, pfnWriteCbk, &writeCbkArg); ASSERT_TRUE(pResult != nullptr); EXPECT_EQ(pResult->nStatus, 124); CPLHTTPDestroyResult(pResult); CPLHTTPSetFetchCallback(nullptr, nullptr); EXPECT_STREQ(userData2.osURL, "http://example.com"); EXPECT_EQ(userData2.papszOptions, aosOptions.List()); EXPECT_EQ(userData2.pfnProgress, pfnProgress); EXPECT_EQ(userData2.pProgressArg, &progressArg); EXPECT_EQ(userData2.pfnWrite, pfnWriteCbk); EXPECT_EQ(userData2.pWriteArg, &writeCbkArg); } // Test CPLLoadConfigOptionsFromFile() and // CPLLoadConfigOptionsFromPredefinedFiles() TEST_F(test_cpl, CPLLoadConfigOptionsFromFile) { CPLLoadConfigOptionsFromFile("/i/do/not/exist", false); VSILFILE *fp = VSIFOpenL("/vsimem/.gdal/gdalrc", "wb"); VSIFPrintfL(fp, "# some comment\n"); VSIFPrintfL(fp, "\n"); // blank line VSIFPrintfL(fp, " \n"); // blank line VSIFPrintfL(fp, "[configoptions]\n"); VSIFPrintfL(fp, "# some comment\n"); VSIFPrintfL(fp, "FOO_CONFIGOPTION=BAR\n"); VSIFCloseL(fp); // Try CPLLoadConfigOptionsFromFile() CPLLoadConfigOptionsFromFile("/vsimem/.gdal/gdalrc", false); ASSERT_TRUE(EQUAL(CPLGetConfigOption("FOO_CONFIGOPTION", ""), "BAR")); CPLSetConfigOption("FOO_CONFIGOPTION", nullptr); // Try CPLLoadConfigOptionsFromPredefinedFiles() with GDAL_CONFIG_FILE set CPLSetConfigOption("GDAL_CONFIG_FILE", "/vsimem/.gdal/gdalrc"); CPLLoadConfigOptionsFromPredefinedFiles(); ASSERT_TRUE(EQUAL(CPLGetConfigOption("FOO_CONFIGOPTION", ""), "BAR")); CPLSetConfigOption("FOO_CONFIGOPTION", nullptr); // Try CPLLoadConfigOptionsFromPredefinedFiles() with $HOME/.gdal/gdalrc // file #ifdef WIN32 const char *pszHOMEEnvVarName = "USERPROFILE"; #else const char *pszHOMEEnvVarName = "HOME"; #endif CPLString osOldVal(CPLGetConfigOption(pszHOMEEnvVarName, "")); CPLSetConfigOption(pszHOMEEnvVarName, "/vsimem/"); CPLLoadConfigOptionsFromPredefinedFiles(); ASSERT_TRUE(EQUAL(CPLGetConfigOption("FOO_CONFIGOPTION", ""), "BAR")); CPLSetConfigOption("FOO_CONFIGOPTION", nullptr); if (!osOldVal.empty()) CPLSetConfigOption(pszHOMEEnvVarName, osOldVal.c_str()); else CPLSetConfigOption(pszHOMEEnvVarName, nullptr); VSIUnlink("/vsimem/.gdal/gdalrc"); } // Test decompressor side of cpl_compressor.h TEST_F(test_cpl, decompressor) { const auto compressionLambda = [](const void * /* input_data */, size_t /* input_size */, void ** /* output_data */, size_t * /* output_size */, CSLConstList /* options */, void * /* compressor_user_data */) { return false; }; int dummy = 0; CPLCompressor sComp; sComp.nStructVersion = 1; sComp.eType = CCT_COMPRESSOR; sComp.pszId = "my_comp"; const char *const apszMetadata[] = {"FOO=BAR", nullptr}; sComp.papszMetadata = apszMetadata; sComp.pfnFunc = compressionLambda; sComp.user_data = &dummy; ASSERT_TRUE(CPLRegisterDecompressor(&sComp)); CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(!CPLRegisterDecompressor(&sComp)); CPLPopErrorHandler(); char **decompressors = CPLGetDecompressors(); ASSERT_TRUE(decompressors != nullptr); EXPECT_TRUE(CSLFindString(decompressors, sComp.pszId) >= 0); for (auto iter = decompressors; *iter; ++iter) { const auto pCompressor = CPLGetDecompressor(*iter); EXPECT_TRUE(pCompressor); if (pCompressor) { const char *pszOptions = CSLFetchNameValue(pCompressor->papszMetadata, "OPTIONS"); if (pszOptions) { auto psNode = CPLParseXMLString(pszOptions); EXPECT_TRUE(psNode); CPLDestroyXMLNode(psNode); } else { CPLDebug("TEST", "Decompressor %s has no OPTIONS", *iter); } } } CSLDestroy(decompressors); EXPECT_TRUE(CPLGetDecompressor("invalid") == nullptr); const auto pCompressor = CPLGetDecompressor(sComp.pszId); ASSERT_TRUE(pCompressor); EXPECT_STREQ(pCompressor->pszId, sComp.pszId); EXPECT_EQ(CSLCount(pCompressor->papszMetadata), CSLCount(sComp.papszMetadata)); EXPECT_TRUE(pCompressor->pfnFunc != nullptr); EXPECT_EQ(pCompressor->user_data, sComp.user_data); CPLDestroyCompressorRegistry(); EXPECT_TRUE(CPLGetDecompressor(sComp.pszId) == nullptr); } // Test compressor side of cpl_compressor.h TEST_F(test_cpl, compressor) { const auto compressionLambda = [](const void * /* input_data */, size_t /* input_size */, void ** /* output_data */, size_t * /* output_size */, CSLConstList /* options */, void * /* compressor_user_data */) { return false; }; int dummy = 0; CPLCompressor sComp; sComp.nStructVersion = 1; sComp.eType = CCT_COMPRESSOR; sComp.pszId = "my_comp"; const char *const apszMetadata[] = {"FOO=BAR", nullptr}; sComp.papszMetadata = apszMetadata; sComp.pfnFunc = compressionLambda; sComp.user_data = &dummy; ASSERT_TRUE(CPLRegisterCompressor(&sComp)); CPLPushErrorHandler(CPLQuietErrorHandler); ASSERT_TRUE(!CPLRegisterCompressor(&sComp)); CPLPopErrorHandler(); char **compressors = CPLGetCompressors(); ASSERT_TRUE(compressors != nullptr); EXPECT_TRUE(CSLFindString(compressors, sComp.pszId) >= 0); for (auto iter = compressors; *iter; ++iter) { const auto pCompressor = CPLGetCompressor(*iter); EXPECT_TRUE(pCompressor); if (pCompressor) { const char *pszOptions = CSLFetchNameValue(pCompressor->papszMetadata, "OPTIONS"); if (pszOptions) { auto psNode = CPLParseXMLString(pszOptions); EXPECT_TRUE(psNode); CPLDestroyXMLNode(psNode); } else { CPLDebug("TEST", "Compressor %s has no OPTIONS", *iter); } } } CSLDestroy(compressors); EXPECT_TRUE(CPLGetCompressor("invalid") == nullptr); const auto pCompressor = CPLGetCompressor(sComp.pszId); ASSERT_TRUE(pCompressor); if (pCompressor == nullptr) return; EXPECT_STREQ(pCompressor->pszId, sComp.pszId); EXPECT_EQ(CSLCount(pCompressor->papszMetadata), CSLCount(sComp.papszMetadata)); EXPECT_TRUE(pCompressor->pfnFunc != nullptr); EXPECT_EQ(pCompressor->user_data, sComp.user_data); CPLDestroyCompressorRegistry(); EXPECT_TRUE(CPLGetDecompressor(sComp.pszId) == nullptr); } // Test builtin compressors/decompressor TEST_F(test_cpl, builtin_compressors) { for (const char *id : {"blosc", "zlib", "gzip", "lzma", "zstd", "lz4"}) { const auto pCompressor = CPLGetCompressor(id); if (pCompressor == nullptr) { CPLDebug("TEST", "%s not available", id); if (strcmp(id, "zlib") == 0 || strcmp(id, "gzip") == 0) { ASSERT_TRUE(false); } continue; } CPLDebug("TEST", "Testing %s", id); const char my_str[] = "my string to compress"; const char *const options[] = {"TYPESIZE=1", nullptr}; // Compressor side // Just get output size size_t out_size = 0; ASSERT_TRUE(pCompressor->pfnFunc(my_str, strlen(my_str), nullptr, &out_size, options, pCompressor->user_data)); ASSERT_TRUE(out_size != 0); // Let it alloc the output buffer void *out_buffer2 = nullptr; size_t out_size2 = 0; ASSERT_TRUE(pCompressor->pfnFunc(my_str, strlen(my_str), &out_buffer2, &out_size2, options, pCompressor->user_data)); ASSERT_TRUE(out_buffer2 != nullptr); ASSERT_TRUE(out_size2 != 0); ASSERT_TRUE(out_size2 <= out_size); std::vector out_buffer3(out_size); // Provide not large enough buffer size size_t out_size3 = 1; void *out_buffer3_ptr = &out_buffer3[0]; ASSERT_TRUE(!(pCompressor->pfnFunc(my_str, strlen(my_str), &out_buffer3_ptr, &out_size3, options, pCompressor->user_data))); // Provide the output buffer out_size3 = out_buffer3.size(); out_buffer3_ptr = &out_buffer3[0]; ASSERT_TRUE(pCompressor->pfnFunc(my_str, strlen(my_str), &out_buffer3_ptr, &out_size3, options, pCompressor->user_data)); ASSERT_TRUE(out_buffer3_ptr != nullptr); ASSERT_TRUE(out_buffer3_ptr == &out_buffer3[0]); ASSERT_TRUE(out_size3 != 0); ASSERT_EQ(out_size3, out_size2); out_buffer3.resize(out_size3); out_buffer3_ptr = &out_buffer3[0]; ASSERT_TRUE(memcmp(out_buffer3_ptr, out_buffer2, out_size2) == 0); CPLFree(out_buffer2); const std::vector compressedData(out_buffer3); // Decompressor side const auto pDecompressor = CPLGetDecompressor(id); ASSERT_TRUE(pDecompressor != nullptr); out_size = 0; ASSERT_TRUE(pDecompressor->pfnFunc( compressedData.data(), compressedData.size(), nullptr, &out_size, nullptr, pDecompressor->user_data)); ASSERT_TRUE(out_size != 0); ASSERT_TRUE(out_size >= strlen(my_str)); out_buffer2 = nullptr; out_size2 = 0; ASSERT_TRUE(pDecompressor->pfnFunc( compressedData.data(), compressedData.size(), &out_buffer2, &out_size2, options, pDecompressor->user_data)); ASSERT_TRUE(out_buffer2 != nullptr); ASSERT_TRUE(out_size2 != 0); ASSERT_EQ(out_size2, strlen(my_str)); ASSERT_TRUE(memcmp(out_buffer2, my_str, strlen(my_str)) == 0); CPLFree(out_buffer2); out_buffer3.clear(); out_buffer3.resize(out_size); out_size3 = out_buffer3.size(); out_buffer3_ptr = &out_buffer3[0]; ASSERT_TRUE(pDecompressor->pfnFunc( compressedData.data(), compressedData.size(), &out_buffer3_ptr, &out_size3, options, pDecompressor->user_data)); ASSERT_TRUE(out_buffer3_ptr != nullptr); ASSERT_TRUE(out_buffer3_ptr == &out_buffer3[0]); ASSERT_EQ(out_size3, strlen(my_str)); ASSERT_TRUE(memcmp(out_buffer3.data(), my_str, strlen(my_str)) == 0); } } template struct TesterDelta { static void test(const char *dtypeOption) { const auto pCompressor = CPLGetCompressor("delta"); ASSERT_TRUE(pCompressor); if (pCompressor == nullptr) return; const auto pDecompressor = CPLGetDecompressor("delta"); ASSERT_TRUE(pDecompressor); if (pDecompressor == nullptr) return; const T tabIn[] = {static_cast(-2), 3, 1}; T tabCompress[3]; T tabOut[3]; const char *const apszOptions[] = {dtypeOption, nullptr}; void *outPtr = &tabCompress[0]; size_t outSize = sizeof(tabCompress); ASSERT_TRUE(pCompressor->pfnFunc(&tabIn[0], sizeof(tabIn), &outPtr, &outSize, apszOptions, pCompressor->user_data)); ASSERT_EQ(outSize, sizeof(tabCompress)); // ASSERT_EQ(tabCompress[0], 2); // ASSERT_EQ(tabCompress[1], 1); // ASSERT_EQ(tabCompress[2], -2); outPtr = &tabOut[0]; outSize = sizeof(tabOut); ASSERT_TRUE(pDecompressor->pfnFunc(&tabCompress[0], sizeof(tabCompress), &outPtr, &outSize, apszOptions, pDecompressor->user_data)); ASSERT_EQ(outSize, sizeof(tabOut)); ASSERT_EQ(tabOut[0], tabIn[0]); ASSERT_EQ(tabOut[1], tabIn[1]); ASSERT_EQ(tabOut[2], tabIn[2]); } }; // Test delta compressor/decompressor TEST_F(test_cpl, delta_compressor) { TesterDelta::test("DTYPE=i1"); TesterDelta::test("DTYPE=u1"); TesterDelta::test("DTYPE=i2"); TesterDelta::test("DTYPE=::test("DTYPE=>i2"); TesterDelta::test("DTYPE=u2"); TesterDelta::test("DTYPE=::test("DTYPE=>u2"); TesterDelta::test("DTYPE=i4"); TesterDelta::test("DTYPE=::test("DTYPE=>i4"); TesterDelta::test("DTYPE=u4"); TesterDelta::test("DTYPE=::test("DTYPE=>u4"); TesterDelta::test("DTYPE=i8"); TesterDelta::test("DTYPE=::test("DTYPE=>i8"); TesterDelta::test("DTYPE=u8"); TesterDelta::test("DTYPE=::test("DTYPE=>u8"); TesterDelta::test("DTYPE=f4"); #ifdef CPL_MSB TesterDelta::test("DTYPE=>f4"); #else TesterDelta::test("DTYPE=::test("DTYPE=f8"); #ifdef CPL_MSB TesterDelta::test("DTYPE=>f8"); #else TesterDelta::test("DTYPE=(static_cast(i)); CPLQuadTreeInsertWithBounds(hTree, hFeature, &rect); } { int nFeatureCount = 0; CPLFree(CPLQuadTreeSearch(hTree, &globalbounds, &nFeatureCount)); ASSERT_EQ(nFeatureCount, 1000); } DummyRandInit(j); for (int i = 0; i < 1000; i++) { CPLRectObj rect; GenerateRandomRect(rect); void *hFeature = reinterpret_cast(static_cast(i)); CPLQuadTreeRemove(hTree, hFeature, &rect); } { int nFeatureCount = 0; CPLFree(CPLQuadTreeSearch(hTree, &globalbounds, &nFeatureCount)); ASSERT_EQ(nFeatureCount, 0); } } CPLQuadTreeDestroy(hTree); } // Test bUnlinkAndSize on VSIGetMemFileBuffer TEST_F(test_cpl, VSIGetMemFileBuffer_unlink_and_size) { VSILFILE *fp = VSIFOpenL("/vsimem/test_unlink_and_seize.tif", "wb"); VSIFWriteL("test", 5, 1, fp); GByte *pRawData = VSIGetMemFileBuffer("/vsimem/test_unlink_and_seize.tif", nullptr, true); ASSERT_TRUE(EQUAL(reinterpret_cast(pRawData), "test")); ASSERT_TRUE(VSIGetMemFileBuffer("/vsimem/test_unlink_and_seize.tif", nullptr, false) == nullptr); ASSERT_TRUE(VSIFOpenL("/vsimem/test_unlink_and_seize.tif", "r") == nullptr); ASSERT_TRUE(VSIFReadL(pRawData, 5, 1, fp) == 0); ASSERT_TRUE(VSIFWriteL(pRawData, 5, 1, fp) == 0); ASSERT_TRUE(VSIFSeekL(fp, 0, SEEK_END) == 0); CPLFree(pRawData); VSIFCloseL(fp); } // Test CPLLoadConfigOptionsFromFile() for VSI credentials TEST_F(test_cpl, CPLLoadConfigOptionsFromFile_VSI_credentials) { VSILFILE *fp = VSIFOpenL("/vsimem/credentials.txt", "wb"); VSIFPrintfL(fp, "[credentials]\n"); VSIFPrintfL(fp, "\n"); VSIFPrintfL(fp, "[.my_subsection]\n"); VSIFPrintfL(fp, "path=/vsi_test/foo/bar\n"); VSIFPrintfL(fp, "FOO=BAR\n"); VSIFPrintfL(fp, "FOO2=BAR2\n"); VSIFPrintfL(fp, "\n"); VSIFPrintfL(fp, "[.my_subsection2]\n"); VSIFPrintfL(fp, "path=/vsi_test/bar/baz\n"); VSIFPrintfL(fp, "BAR=BAZ\n"); VSIFPrintfL(fp, "[configoptions]\n"); VSIFPrintfL(fp, "configoptions_FOO=BAR\n"); VSIFCloseL(fp); CPLErrorReset(); CPLLoadConfigOptionsFromFile("/vsimem/credentials.txt", false); ASSERT_EQ(CPLGetLastErrorType(), CE_None); { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/foo/bar", "FOO", nullptr); ASSERT_TRUE(pszVal != nullptr); ASSERT_EQ(std::string(pszVal), std::string("BAR")); } { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/foo/bar", "FOO2", nullptr); ASSERT_TRUE(pszVal != nullptr); ASSERT_EQ(std::string(pszVal), std::string("BAR2")); } { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/bar/baz", "BAR", nullptr); ASSERT_TRUE(pszVal != nullptr); ASSERT_EQ(std::string(pszVal), std::string("BAZ")); } { const char *pszVal = CPLGetConfigOption("configoptions_FOO", nullptr); ASSERT_TRUE(pszVal != nullptr); ASSERT_EQ(std::string(pszVal), std::string("BAR")); } VSIClearPathSpecificOptions("/vsi_test/bar/baz"); CPLSetConfigOption("configoptions_FOO", nullptr); { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/bar/baz", "BAR", nullptr); ASSERT_TRUE(pszVal == nullptr); } VSIUnlink("/vsimem/credentials.txt"); } // Test CPLLoadConfigOptionsFromFile() for VSI credentials, warning case TEST_F(test_cpl, CPLLoadConfigOptionsFromFile_VSI_credentials_warning) { VSILFILE *fp = VSIFOpenL("/vsimem/credentials.txt", "wb"); VSIFPrintfL(fp, "[credentials]\n"); VSIFPrintfL(fp, "\n"); VSIFPrintfL(fp, "FOO=BAR\n"); // content outside of subsection VSIFCloseL(fp); CPLErrorReset(); CPLPushErrorHandler(CPLQuietErrorHandler); CPLLoadConfigOptionsFromFile("/vsimem/credentials.txt", false); CPLPopErrorHandler(); ASSERT_EQ(CPLGetLastErrorType(), CE_Warning); VSIUnlink("/vsimem/credentials.txt"); } // Test CPLLoadConfigOptionsFromFile() for VSI credentials, warning case TEST_F(test_cpl, CPLLoadConfigOptionsFromFile_VSI_credentials_subsection_warning) { VSILFILE *fp = VSIFOpenL("/vsimem/credentials.txt", "wb"); VSIFPrintfL(fp, "[credentials]\n"); VSIFPrintfL(fp, "[.subsection]\n"); VSIFPrintfL(fp, "FOO=BAR\n"); // first key is not 'path' VSIFCloseL(fp); CPLErrorReset(); CPLPushErrorHandler(CPLQuietErrorHandler); CPLLoadConfigOptionsFromFile("/vsimem/credentials.txt", false); CPLPopErrorHandler(); ASSERT_EQ(CPLGetLastErrorType(), CE_Warning); VSIUnlink("/vsimem/credentials.txt"); } // Test CPLLoadConfigOptionsFromFile() for VSI credentials, warning case TEST_F(test_cpl, CPLLoadConfigOptionsFromFile_VSI_credentials_warning_path_specific) { VSILFILE *fp = VSIFOpenL("/vsimem/credentials.txt", "wb"); VSIFPrintfL(fp, "[credentials]\n"); VSIFPrintfL(fp, "[.subsection]\n"); VSIFPrintfL(fp, "path=/vsi_test/foo\n"); VSIFPrintfL(fp, "path=/vsi_test/bar\n"); // duplicated path VSIFPrintfL(fp, "FOO=BAR\n"); // first key is not 'path' VSIFPrintfL(fp, "[unrelated_section]"); VSIFPrintfL(fp, "BAR=BAZ\n"); // first key is not 'path' VSIFCloseL(fp); CPLErrorReset(); CPLPushErrorHandler(CPLQuietErrorHandler); CPLLoadConfigOptionsFromFile("/vsimem/credentials.txt", false); CPLPopErrorHandler(); ASSERT_EQ(CPLGetLastErrorType(), CE_Warning); { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/foo", "FOO", nullptr); ASSERT_TRUE(pszVal != nullptr); } { const char *pszVal = VSIGetPathSpecificOption("/vsi_test/foo", "BAR", nullptr); ASSERT_TRUE(pszVal == nullptr); } VSIUnlink("/vsimem/credentials.txt"); } // Test CPLRecodeFromWCharIconv() with 2 bytes/char source encoding TEST_F(test_cpl, CPLRecodeFromWCharIconv_2byte_source_encoding) { #ifdef CPL_RECODE_ICONV int N = 2048; wchar_t *pszIn = static_cast(CPLMalloc((N + 1) * sizeof(wchar_t))); for (int i = 0; i < N; i++) pszIn[i] = L'A'; pszIn[N] = L'\0'; char *pszExpected = static_cast(CPLMalloc(N + 1)); for (int i = 0; i < N; i++) pszExpected[i] = 'A'; pszExpected[N] = '\0'; char *pszRet = CPLRecodeFromWChar(pszIn, CPL_ENC_UTF16, CPL_ENC_UTF8); const bool bOK = memcmp(pszExpected, pszRet, N + 1) == 0; // FIXME Some tests fail on Mac. Not sure why, but do not error out just for // that if (!bOK && (strstr(CPLGetConfigOption("TRAVIS_OS_NAME", ""), "osx") != nullptr || strstr(CPLGetConfigOption("BUILD_NAME", ""), "osx") != nullptr || getenv("DO_NOT_FAIL_ON_RECODE_ERRORS") != nullptr)) { fprintf(stderr, "Recode from CPL_ENC_UTF16 to CPL_ENC_UTF8 failed\n"); } else { EXPECT_TRUE(bOK); } CPLFree(pszIn); CPLFree(pszRet); CPLFree(pszExpected); #else GTEST_SKIP() << "iconv support missing"; #endif } // VERY MINIMAL testing of VSI plugin functionality TEST_F(test_cpl, VSI_plugin_minimal_testing) { auto psCallbacks = VSIAllocFilesystemPluginCallbacksStruct(); psCallbacks->open = [](void *pUserData, const char *pszFilename, const char *pszAccess) -> void * { (void)pUserData; if (strcmp(pszFilename, "test") == 0 && strcmp(pszAccess, "rb") == 0) return const_cast("ok"); return nullptr; }; EXPECT_EQ(VSIInstallPluginHandler("/vsimyplugin/", psCallbacks), 0); VSIFreeFilesystemPluginCallbacksStruct(psCallbacks); VSILFILE *fp = VSIFOpenL("/vsimyplugin/test", "rb"); EXPECT_TRUE(fp != nullptr); // Check it doesn't crash vsi_l_offset nOffset = 5; size_t nSize = 10; reinterpret_cast(fp)->AdviseRead(1, &nOffset, &nSize); VSIFCloseL(fp); EXPECT_TRUE(VSIFOpenL("/vsimyplugin/i_dont_exist", "rb") == nullptr); } TEST_F(test_cpl, VSI_plugin_advise_read) { auto psCallbacks = VSIAllocFilesystemPluginCallbacksStruct(); struct UserData { int nRanges = 0; const vsi_l_offset *panOffsets = nullptr; const size_t *panSizes = nullptr; }; UserData userData; psCallbacks->pUserData = &userData; psCallbacks->open = [](void *pUserData, const char * /*pszFilename*/, const char * /*pszAccess*/) -> void * { return pUserData; }; psCallbacks->advise_read = [](void *pFile, int nRanges, const vsi_l_offset *panOffsets, const size_t *panSizes) { static_cast(pFile)->nRanges = nRanges; static_cast(pFile)->panOffsets = panOffsets; static_cast(pFile)->panSizes = panSizes; }; EXPECT_EQ(VSIInstallPluginHandler("/VSI_plugin_advise_read/", psCallbacks), 0); VSIFreeFilesystemPluginCallbacksStruct(psCallbacks); VSILFILE *fp = VSIFOpenL("/VSI_plugin_advise_read/test", "rb"); EXPECT_TRUE(fp != nullptr); vsi_l_offset nOffset = 5; size_t nSize = 10; reinterpret_cast(fp)->AdviseRead(1, &nOffset, &nSize); EXPECT_EQ(userData.nRanges, 1); EXPECT_EQ(userData.panOffsets, &nOffset); EXPECT_EQ(userData.panSizes, &nSize); VSIFCloseL(fp); } // Test CPLIsASCII() TEST_F(test_cpl, CPLIsASCII) { ASSERT_TRUE(CPLIsASCII("foo", 3)); ASSERT_TRUE(CPLIsASCII("foo", static_cast(-1))); ASSERT_TRUE(!CPLIsASCII("\xFF", 1)); } // Test VSIIsLocal() TEST_F(test_cpl, VSIIsLocal) { ASSERT_TRUE(VSIIsLocal("/vsimem/")); ASSERT_TRUE(VSIIsLocal("/vsigzip//vsimem/tmp.gz")); #ifdef HAVE_CURL ASSERT_TRUE(!VSIIsLocal("/vsicurl/http://example.com")); #endif VSIStatBufL sStat; #ifdef _WIN32 if (VSIStatL("c:\\", &sStat) == 0) { ASSERT_TRUE(VSIIsLocal("c:\\i_do_not_exist")); } #else if (VSIStatL("/tmp", &sStat) == 0) { ASSERT_TRUE(VSIIsLocal("/tmp/i_do_not_exist")); } #endif } // Test VSISupportsSequentialWrite() TEST_F(test_cpl, VSISupportsSequentialWrite) { ASSERT_TRUE(VSISupportsSequentialWrite("/vsimem/", false)); #ifdef HAVE_CURL ASSERT_TRUE( !VSISupportsSequentialWrite("/vsicurl/http://example.com", false)); ASSERT_TRUE(VSISupportsSequentialWrite("/vsis3/test_bucket/", false)); #endif ASSERT_TRUE(VSISupportsSequentialWrite("/vsigzip//vsimem/tmp.gz", false)); #ifdef HAVE_CURL ASSERT_TRUE(!VSISupportsSequentialWrite( "/vsigzip//vsicurl/http://example.com/tmp.gz", false)); #endif VSIStatBufL sStat; #ifdef _WIN32 if (VSIStatL("c:\\", &sStat) == 0) { ASSERT_TRUE(VSISupportsSequentialWrite("c:\\", false)); } #else if (VSIStatL("/tmp", &sStat) == 0) { ASSERT_TRUE(VSISupportsSequentialWrite("/tmp/i_do_not_exist", false)); } #endif } // Test VSISupportsRandomWrite() TEST_F(test_cpl, VSISupportsRandomWrite) { ASSERT_TRUE(VSISupportsRandomWrite("/vsimem/", false)); #ifdef HAVE_CURL ASSERT_TRUE(!VSISupportsRandomWrite("/vsicurl/http://example.com", false)); ASSERT_TRUE(!VSISupportsRandomWrite("/vsis3/test_bucket/", false)); ASSERT_TRUE(!VSISupportsRandomWrite("/vsis3/test_bucket/", true)); CPLSetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "YES"); ASSERT_TRUE(VSISupportsRandomWrite("/vsis3/test_bucket/", true)); CPLSetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", nullptr); #endif ASSERT_TRUE(!VSISupportsRandomWrite("/vsigzip//vsimem/tmp.gz", false)); #ifdef HAVE_CURL ASSERT_TRUE(!VSISupportsRandomWrite( "/vsigzip//vsicurl/http://example.com/tmp.gz", false)); #endif VSIStatBufL sStat; #ifdef _WIN32 if (VSIStatL("c:\\", &sStat) == 0) { ASSERT_TRUE(VSISupportsRandomWrite("c:\\", false)); } #else if (VSIStatL("/tmp", &sStat) == 0) { ASSERT_TRUE(VSISupportsRandomWrite("/tmp", false)); } #endif } // Test ignore-env-vars = yes of configuration file TEST_F(test_cpl, config_file_ignore_env_vars) { char szEnvVar[] = "SOME_ENV_VAR_FOR_TEST_CPL_61=FOO"; putenv(szEnvVar); ASSERT_TRUE(CPLGetConfigOption("SOME_ENV_VAR_FOR_TEST_CPL_61", nullptr) != nullptr); VSILFILE *fp = VSIFOpenL("/vsimem/.gdal/gdalrc", "wb"); VSIFPrintfL(fp, "[directives]\n"); VSIFPrintfL(fp, "ignore-env-vars=yes\n"); VSIFPrintfL(fp, "[configoptions]\n"); VSIFPrintfL(fp, "CONFIG_OPTION_FOR_TEST_CPL_61=BAR\n"); VSIFCloseL(fp); // Load configuration file CPLLoadConfigOptionsFromFile("/vsimem/.gdal/gdalrc", false); // Check that reading configuration option works ASSERT_TRUE( EQUAL(CPLGetConfigOption("CONFIG_OPTION_FOR_TEST_CPL_61", ""), "BAR")); // Check that environment variables are not read as configuration options ASSERT_TRUE(CPLGetConfigOption("SOME_ENV_VAR_FOR_TEST_CPL_61", nullptr) == nullptr); // Reset ignore-env-vars=no fp = VSIFOpenL("/vsimem/.gdal/gdalrc", "wb"); VSIFPrintfL(fp, "[directives]\n"); VSIFPrintfL(fp, "ignore-env-vars=no\n"); VSIFPrintfL(fp, "[configoptions]\n"); VSIFPrintfL(fp, "SOME_ENV_VAR_FOR_TEST_CPL_61=BAR\n"); VSIFCloseL(fp); // Reload configuration file CPLLoadConfigOptionsFromFile("/vsimem/.gdal/gdalrc", false); // Check that environment variables are read as configuration options // and override configuration options ASSERT_TRUE(CPLGetConfigOption("SOME_ENV_VAR_FOR_TEST_CPL_61", nullptr) != nullptr); ASSERT_EQ( std::string(CPLGetConfigOption("SOME_ENV_VAR_FOR_TEST_CPL_61", "")), std::string("FOO")); VSIUnlink("/vsimem/.gdal/gdalrc"); } // Test CPLWorkerThreadPool recursion TEST_F(test_cpl, CPLWorkerThreadPool_recursion) { struct Context { CPLWorkerThreadPool oThreadPool{}; std::atomic nCounter{0}; std::mutex mutex{}; std::condition_variable cv{}; bool you_can_leave = false; int threadStarted = 0; }; Context ctxt; ctxt.oThreadPool.Setup(2, nullptr, nullptr, /* waitAllStarted = */ true); struct Data { Context *psCtxt; int iJob; GIntBig nThreadLambda = 0; Data(Context *psCtxtIn, int iJobIn) : psCtxt(psCtxtIn), iJob(iJobIn) { } Data(const Data &) = default; }; const auto lambda = [](void *pData) { auto psData = static_cast(pData); if (psData->iJob > 0) { // wait for both threads to be started std::unique_lock guard(psData->psCtxt->mutex); psData->psCtxt->threadStarted++; psData->psCtxt->cv.notify_one(); while (psData->psCtxt->threadStarted < 2) { psData->psCtxt->cv.wait(guard); } } psData->nThreadLambda = CPLGetPID(); // fprintf(stderr, "lambda %d: " CPL_FRMT_GIB "\n", // psData->iJob, psData->nThreadLambda); const auto lambda2 = [](void *pData2) { const auto psData2 = static_cast(pData2); const int iJob = psData2->iJob; const int nCounter = psData2->psCtxt->nCounter++; CPL_IGNORE_RET_VAL(nCounter); const auto nThreadLambda2 = CPLGetPID(); // fprintf(stderr, "lambda2 job=%d, counter(before)=%d, thread=" // CPL_FRMT_GIB "\n", iJob, nCounter, nThreadLambda2); if (iJob == 100 + 0) { ASSERT_TRUE(nThreadLambda2 != psData2->nThreadLambda); // make sure that job 0 run in the other thread // takes sufficiently long that job 2 has been submitted // before it completes std::unique_lock guard(psData2->psCtxt->mutex); while (!psData2->psCtxt->you_can_leave) { psData2->psCtxt->cv.wait(guard); } } else if (iJob == 100 + 1 || iJob == 100 + 2) { ASSERT_TRUE(nThreadLambda2 == psData2->nThreadLambda); } }; auto poQueue = psData->psCtxt->oThreadPool.CreateJobQueue(); Data d0(*psData); d0.iJob = 100 + d0.iJob * 3 + 0; Data d1(*psData); d1.iJob = 100 + d1.iJob * 3 + 1; Data d2(*psData); d2.iJob = 100 + d2.iJob * 3 + 2; poQueue->SubmitJob(lambda2, &d0); poQueue->SubmitJob(lambda2, &d1); poQueue->SubmitJob(lambda2, &d2); if (psData->iJob == 0) { std::lock_guard guard(psData->psCtxt->mutex); psData->psCtxt->you_can_leave = true; psData->psCtxt->cv.notify_one(); } }; { auto poQueue = ctxt.oThreadPool.CreateJobQueue(); Data data0(&ctxt, 0); poQueue->SubmitJob(lambda, &data0); } { auto poQueue = ctxt.oThreadPool.CreateJobQueue(); Data data1(&ctxt, 1); Data data2(&ctxt, 2); poQueue->SubmitJob(lambda, &data1); poQueue->SubmitJob(lambda, &data2); } ASSERT_EQ(ctxt.nCounter, 3 * 3); } // Test /vsimem/ PRead() implementation TEST_F(test_cpl, vsimem_pread) { char szContent[] = "abcd"; VSILFILE *fp = VSIFileFromMemBuffer( "", reinterpret_cast(szContent), 4, FALSE); VSIVirtualHandle *poHandle = reinterpret_cast(fp); ASSERT_TRUE(poHandle->HasPRead()); { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 2, 1), 2U); ASSERT_EQ(std::string(szBuffer), std::string("bc")); } { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 4, 1), 3U); ASSERT_EQ(std::string(szBuffer), std::string("bcd")); } { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 1, 4), 0U); ASSERT_EQ(std::string(szBuffer), std::string()); } VSIFCloseL(fp); } // Test regular file system PRead() implementation TEST_F(test_cpl, file_system_pread) { VSILFILE *fp = VSIFOpenL("temp_test_64.bin", "wb+"); if (fp == nullptr) return; VSIVirtualHandle *poHandle = reinterpret_cast(fp); poHandle->Write("abcd", 4, 1); if (poHandle->HasPRead()) { poHandle->Flush(); { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 2, 1), 2U); ASSERT_EQ(std::string(szBuffer), std::string("bc")); } { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 4, 1), 3U); ASSERT_EQ(std::string(szBuffer), std::string("bcd")); } { char szBuffer[5] = {0}; ASSERT_EQ(poHandle->PRead(szBuffer, 1, 4), 0U); ASSERT_EQ(std::string(szBuffer), std::string()); } } VSIFCloseL(fp); VSIUnlink("temp_test_64.bin"); } // Test CPLMask implementation TEST_F(test_cpl, CPLMask) { constexpr std::size_t sz = 71; auto m = CPLMaskCreate(sz, true); // Mask is set by default for (std::size_t i = 0; i < sz; i++) { EXPECT_EQ(CPLMaskGet(m, i), true) << "bit " << i; } VSIFree(m); m = CPLMaskCreate(sz, false); auto m2 = CPLMaskCreate(sz, false); // Mask is unset by default for (std::size_t i = 0; i < sz; i++) { EXPECT_EQ(CPLMaskGet(m, i), false) << "bit " << i; } // Set a few bits CPLMaskSet(m, 10); CPLMaskSet(m, 33); CPLMaskSet(m, 70); // Check all bits for (std::size_t i = 0; i < sz; i++) { if (i == 10 || i == 33 || i == 70) { EXPECT_EQ(CPLMaskGet(m, i), true) << "bit " << i; } else { EXPECT_EQ(CPLMaskGet(m, i), false) << "bit " << i; } } // Unset some bits CPLMaskClear(m, 10); CPLMaskClear(m, 70); // Check all bits for (std::size_t i = 0; i < sz; i++) { if (i == 33) { EXPECT_EQ(CPLMaskGet(m, i), true) << "bit " << i; } else { EXPECT_EQ(CPLMaskGet(m, i), false) << "bit " << i; } } CPLMaskSet(m2, 36); CPLMaskMerge(m2, m, sz); // Check all bits for (std::size_t i = 0; i < sz; i++) { if (i == 36 || i == 33) { ASSERT_EQ(CPLMaskGet(m2, i), true) << "bit " << i; } else { ASSERT_EQ(CPLMaskGet(m2, i), false) << "bit " << i; } } CPLMaskClearAll(m, sz); CPLMaskSetAll(m2, sz); // Check all bits for (std::size_t i = 0; i < sz; i++) { EXPECT_EQ(CPLMaskGet(m, i), false) << "bit " << i; EXPECT_EQ(CPLMaskGet(m2, i), true) << "bit " << i; } VSIFree(m); VSIFree(m2); } // Test cpl::ThreadSafeQueue TEST_F(test_cpl, ThreadSafeQueue) { cpl::ThreadSafeQueue queue; ASSERT_TRUE(queue.empty()); ASSERT_EQ(queue.size(), 0U); queue.push(1); ASSERT_TRUE(!queue.empty()); ASSERT_EQ(queue.size(), 1U); queue.clear(); ASSERT_TRUE(queue.empty()); ASSERT_EQ(queue.size(), 0U); int val = 10; queue.push(std::move(val)); ASSERT_TRUE(!queue.empty()); ASSERT_EQ(queue.size(), 1U); ASSERT_EQ(queue.get_and_pop_front(), 10); ASSERT_TRUE(queue.empty()); } TEST_F(test_cpl, CPLGetExecPath) { std::vector achBuffer(1024, 'x'); if (!CPLGetExecPath(achBuffer.data(), static_cast(achBuffer.size()))) { GTEST_SKIP() << "CPLGetExecPath() not implemented for this platform"; return; } bool bFoundNulTerminatedChar = false; for (char ch : achBuffer) { if (ch == '\0') { bFoundNulTerminatedChar = true; break; } } ASSERT_TRUE(bFoundNulTerminatedChar); // Check that the file exists VSIStatBufL sStat; EXPECT_EQ(VSIStatL(achBuffer.data(), &sStat), 0); const std::string osStrBefore(achBuffer.data()); // Resize the buffer to just the minimum size achBuffer.resize(strlen(achBuffer.data()) + 1); EXPECT_TRUE( CPLGetExecPath(achBuffer.data(), static_cast(achBuffer.size()))); EXPECT_STREQ(osStrBefore.c_str(), achBuffer.data()); // Too small buffer achBuffer.resize(achBuffer.size() - 1); EXPECT_FALSE( CPLGetExecPath(achBuffer.data(), static_cast(achBuffer.size()))); } TEST_F(test_cpl, VSIDuplicateFileSystemHandler) { { CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); EXPECT_FALSE(VSIDuplicateFileSystemHandler( "/vsi_i_dont_exist/", "/vsi_i_will_not_be_created/")); } { CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); EXPECT_FALSE( VSIDuplicateFileSystemHandler("/", "/vsi_i_will_not_be_created/")); } EXPECT_EQ(VSIFileManager::GetHandler("/vsi_test_clone_vsimem/"), VSIFileManager::GetHandler("/")); EXPECT_TRUE( VSIDuplicateFileSystemHandler("/vsimem/", "/vsi_test_clone_vsimem/")); EXPECT_NE(VSIFileManager::GetHandler("/vsi_test_clone_vsimem/"), VSIFileManager::GetHandler("/")); { CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); EXPECT_FALSE(VSIDuplicateFileSystemHandler("/vsimem/", "/vsi_test_clone_vsimem/")); } } TEST_F(test_cpl, CPLAtoGIntBigEx) { { int bOverflow = 0; EXPECT_EQ(CPLAtoGIntBigEx("9223372036854775807", false, &bOverflow), std::numeric_limits::max()); EXPECT_EQ(bOverflow, FALSE); } { int bOverflow = 0; EXPECT_EQ(CPLAtoGIntBigEx("9223372036854775808", false, &bOverflow), std::numeric_limits::max()); EXPECT_EQ(bOverflow, TRUE); } { int bOverflow = 0; EXPECT_EQ(CPLAtoGIntBigEx("-9223372036854775808", false, &bOverflow), std::numeric_limits::min()); EXPECT_EQ(bOverflow, FALSE); } { int bOverflow = 0; EXPECT_EQ(CPLAtoGIntBigEx("-9223372036854775809", false, &bOverflow), std::numeric_limits::min()); EXPECT_EQ(bOverflow, TRUE); } } TEST_F(test_cpl, CPLSubscribeToSetConfigOption) { struct Event { std::string osKey; std::string osValue; bool bThreadLocal; }; std::vector events; const auto cbk = +[](const char *pszKey, const char *pszValue, bool bThreadLocal, void *pUserData) { std::vector *pEvents = static_cast *>(pUserData); Event ev; ev.osKey = pszKey; ev.osValue = pszValue ? pszValue : ""; ev.bThreadLocal = bThreadLocal; pEvents->emplace_back(ev); }; // Subscribe and unsubscribe immediately { int nId = CPLSubscribeToSetConfigOption(cbk, &events); CPLSetConfigOption("CPLSubscribeToSetConfigOption", "bar"); EXPECT_EQ(events.size(), 1U); if (!events.empty()) { EXPECT_STREQ(events[0].osKey.c_str(), "CPLSubscribeToSetConfigOption"); EXPECT_STREQ(events[0].osValue.c_str(), "bar"); EXPECT_FALSE(events[0].bThreadLocal); } CPLUnsubscribeToSetConfigOption(nId); } events.clear(); // Subscribe and unsubscribe in non-nested order { int nId1 = CPLSubscribeToSetConfigOption(cbk, &events); int nId2 = CPLSubscribeToSetConfigOption(cbk, &events); CPLUnsubscribeToSetConfigOption(nId1); int nId3 = CPLSubscribeToSetConfigOption(cbk, &events); CPLSetConfigOption("CPLSubscribeToSetConfigOption", nullptr); EXPECT_EQ(events.size(), 2U); CPLUnsubscribeToSetConfigOption(nId2); CPLUnsubscribeToSetConfigOption(nId3); CPLSetConfigOption("CPLSubscribeToSetConfigOption", nullptr); EXPECT_EQ(events.size(), 2U); } } } // namespace