Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(core): Add support for retrying requests in network_reader_with_valid_http_header_kv_pairs on read failures to mitigate server-side errors. #613

Merged

Conversation

anlowee
Copy link
Contributor

@anlowee anlowee commented Nov 26, 2024

Description

Allow up to 10 trials when unitesting network_reader_with_valid_http_header_kv_pairs and network_reader_with_illegal_http_header_kv_pairs

Validation performed

Run these two unittest cases and get passed.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced test reliability for the NetworkReader class by adding retry logic for HTTP requests, reducing the likelihood of test failures due to transient issues.

Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Walkthrough

The pull request introduces modifications to the test cases for the NetworkReader class in test-NetworkReader.cpp. It implements retry logic for HTTP requests in the network_reader_with_valid_http_header_kv_pairs test case, allowing up to 10 attempts in the event of transient failures. The content is now stored in an std::optional<std::vector<char>> to handle potential absence of content. Additionally, the type of cNumHttpHeaderKeyValuePairs was changed from int to size_t for consistency.

Changes

File Change Summary
components/core/tests/test-NetworkReader.cpp Added retry logic to network_reader_with_valid_http_header_kv_pairs, allowing up to 10 attempts for HTTP requests to handle transient failures. Updated content storage to std::optional<std::vector<char>> and changed cNumHttpHeaderKeyValuePairs from int to size_t. The network_reader_with_illegal_http_header_kv_pairs test case remains unchanged in logic.

Possibly related PRs

Suggested reviewers

  • LinZhihao-723

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@anlowee anlowee marked this pull request as ready for review November 26, 2024 22:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
components/core/tests/test-NetworkReader.cpp (4)

196-198: Improve comment clarity and grammar

The comment could be more professionally worded. Consider revising for clarity and grammar.

-    // Since the http request sometimes crash due to arbitrary reasons, so we just try it
-    // multiple times to avoid such cases
+    // Implement retry logic to handle transient HTTP request failures

209-240: Consider adding timeout between retries and improve error reporting

The retry logic could be enhanced to:

  1. Add a small delay between retries to prevent overwhelming the server
  2. Log the specific failure reason for each attempt to aid debugging
     bool is_pass{false};
     for (size_t i{0}; i < cNumMaxTrials; ++i) {
+        if (i > 0) {
+            // Add exponential backoff delay
+            std::this_thread::sleep_for(std::chrono::milliseconds{100 * (1 << i)});
+        }
         clp::NetworkReader reader{
                 "https://httpbin.org/headers",
                 0,
                 false,
                 clp::CurlDownloadHandler::cDefaultOverallTimeout,
                 clp::CurlDownloadHandler::cDefaultConnectionTimeout,
                 clp::NetworkReader::cDefaultBufferPoolSize,
                 clp::NetworkReader::cDefaultBufferSize,
                 valid_http_header_kv_pairs
         };
         auto const content{get_content(reader)};
         if (false == assert_curl_error_code(CURLE_OK, reader)) {
+            WARN(fmt::format("Attempt {} failed: CURL error", i + 1));
             continue;
         }
         auto const parsed_content = nlohmann::json::parse(content);
         auto const& headers{parsed_content.at("headers")};
         bool are_headers_same{true};
         for (auto const& [key, value] : valid_http_header_kv_pairs) {
             if (value != headers.at(key).get<std::string_view>()) {
                 are_headers_same = false;
+                WARN(fmt::format("Attempt {} failed: Header mismatch for key {}", i + 1, key));
                 break;
             }
         }
         if (!are_headers_same) {
             continue;
         }
         is_pass = true;
         break;
     }

244-246: Improve comment clarity and grammar

Similar to the previous test case, the comment should be revised for clarity and professionalism.

-    // Since the http request sometimes crash due to arbitrary reasons, so we just try it
-    // multiple times to avoid such cases
+    // Implement retry logic to handle transient HTTP request failures

Consider adding test configuration for httpbin.org endpoint

The test suite would benefit from the following changes:

  • Add a test configuration mechanism in components/core/tests/ to allow configuring external service endpoints
  • Define a timeout configuration alongside other network timeouts in CurlDownloadHandler
  • Implement a test skip mechanism when the external service is unreachable

Locations requiring attention:

  • components/core/tests/test-NetworkReader.cpp: Add configuration for httpbin.org endpoint
  • components/core/src/clp/CurlDownloadHandler: Add external service timeout configuration

The codebase shows no existing test configuration mechanism for external services, despite having various other configuration systems (GlobalMetadataDBConfig, RegexToWildcardTranslatorConfig). The NetworkReader tests are tightly coupled to httpbin.org, making them potentially fragile and dependent on external network conditions.

🔗 Analysis chain

Line range hint 196-279: Consider adding test configuration for httpbin.org endpoint

The tests rely on an external service (httpbin.org). Consider:

  1. Making the endpoint configurable to allow testing against a local instance
  2. Adding a timeout configuration for the external service
  3. Adding a skip mechanism when the service is unavailable

Let me check if there are any test utilities or configuration mechanisms in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if httpbin.org is used elsewhere in the codebase
rg --type cpp "httpbin.org" .

# Check if there are any existing test configurations
fd -e hpp -e cpp -e h "test.*config" .

Length of output: 404


Script:

#!/bin/bash
# Check for test utilities or helper files
fd -e hpp -e cpp -e h "test.*util|test.*helper" .

# Check for any configuration or environment setup in test files
rg -g '*.cpp' -g '*.hpp' -g '*.h' "TEST_SETUP|ENVIRONMENT|Config" .

Length of output: 13494

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7aea626 and 2bde441.

📒 Files selected for processing (1)
  • components/core/tests/test-NetworkReader.cpp (3 hunks)
🧰 Additional context used
📓 Learnings (1)
components/core/tests/test-NetworkReader.cpp (1)
Learnt from: LinZhihao-723
PR: y-scope/clp#593
File: components/core/tests/test-NetworkReader.cpp:216-219
Timestamp: 2024-11-15T03:15:45.919Z
Learning: In the `network_reader_with_valid_http_header_kv_pairs` test case in `components/core/tests/test-NetworkReader.cpp`, additional error handling for JSON parsing failures is not necessary, as the current error message is considered sufficient.

Comment on lines 257 to 279
bool is_pass{false};
for (size_t i{0}; i < cNumMaxTrials; ++i) {
clp::NetworkReader reader{
"https://httpbin.org/headers",
0,
false,
clp::CurlDownloadHandler::cDefaultOverallTimeout,
clp::CurlDownloadHandler::cDefaultConnectionTimeout,
clp::NetworkReader::cDefaultBufferPoolSize,
clp::NetworkReader::cDefaultBufferSize,
illegal_header_kv_pairs
};
auto const content = get_content(reader);
if (false == content.empty()) {
continue;
}
if (false == assert_curl_error_code(CURLE_BAD_FUNCTION_ARGUMENT, reader)) {
continue;
}
is_pass = true;
break;
}
REQUIRE(is_pass);
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting retry logic to a helper function

Both test cases use similar retry logic. Consider extracting this pattern into a helper function to improve code reuse and maintainability.

namespace {
template<typename TestFunc>
bool retry_test(TestFunc test_func, int max_trials = 10) {
    for (size_t i{0}; i < max_trials; ++i) {
        if (i > 0) {
            std::this_thread::sleep_for(std::chrono::milliseconds{100 * (1 << i)});
        }
        if (test_func(i)) {
            return true;
        }
    }
    return false;
}
}  // namespace

// Usage example:
bool is_pass = retry_test([&](size_t attempt) {
    clp::NetworkReader reader{/*...*/};
    auto const content = get_content(reader);
    if (!content.empty()) {
        WARN(fmt::format("Attempt {} failed: Non-empty content", attempt + 1));
        return false;
    }
    if (!assert_curl_error_code(CURLE_BAD_FUNCTION_ARGUMENT, reader)) {
        WARN(fmt::format("Attempt {} failed: Unexpected error code", attempt + 1));
        return false;
    }
    return true;
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment makes sense. Let's add a template helper function to reduce code duplication

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LinZhihao-723, glad to hear that you agree. Here's how you can add a template helper function to reduce code duplication:

First, define the helper function retry_test in the anonymous namespace at the top of your test file:

namespace {
template<typename TestFunc>
bool retry_test(TestFunc test_func, int max_trials = 10) {
    for (size_t i{0}; i < max_trials; ++i) {
        if (test_func(i)) {
            return true;
        }
        // Optional: Add a short delay between retries if needed
        // std::this_thread::sleep_for(std::chrono::milliseconds{100});
    }
    return false;
}
}  // namespace

Then, modify your test cases to utilize this helper function.

For the network_reader_with_valid_http_header_kv_pairs test case:

TEST_CASE("network_reader_with_valid_http_header_kv_pairs", "[NetworkReader]") {
    std::unordered_map<std::string, std::string> valid_http_header_kv_pairs;
    constexpr int cNumHttpHeaderKeyValuePairs{10};
    for (size_t i{0}; i < cNumHttpHeaderKeyValuePairs; ++i) {
        valid_http_header_kv_pairs.emplace(
            fmt::format("Unit-Test-Key{}", i),
            fmt::format("Unit-Test-Value{}", i)
        );
    }

    bool is_pass = retry_test([&](size_t attempt) {
        clp::NetworkReader reader{
            "https://httpbin.org/headers",
            0,
            false,
            clp::CurlDownloadHandler::cDefaultOverallTimeout,
            clp::CurlDownloadHandler::cDefaultConnectionTimeout,
            clp::NetworkReader::cDefaultBufferPoolSize,
            clp::NetworkReader::cDefaultBufferSize,
            valid_http_header_kv_pairs
        };
        auto const content{get_content(reader)};
        if (!assert_curl_error_code(CURLE_OK, reader)) {
            return false;
        }
        auto const parsed_content = nlohmann::json::parse(content);
        auto const& headers{parsed_content.at("headers")};
        for (auto const& [key, value] : valid_http_header_kv_pairs) {
            if (value != headers.at(key).get<std::string>()) {
                return false;
            }
        }
        return true;
    });
    REQUIRE(is_pass);
}

And for the network_reader_with_illegal_http_header_kv_pairs test case:

TEST_CASE("network_reader_with_illegal_http_header_kv_pairs", "[NetworkReader]") {
    auto illegal_header_kv_pairs = GENERATE(
        std::unordered_map<std::string, std::string>{{"Range", "bytes=100-"}},
        std::unordered_map<std::string, std::string>{{"RAnGe", "bytes=100-"}},
        std::unordered_map<std::string, std::string>{{"Cache-Control", "no-cache"}},
        std::unordered_map<std::string, std::string>{{"Pragma", "no-cache"}},
        std::unordered_map<std::string, std::string>{{"Legal-Name", "CRLF\r\n"}}
    );

    bool is_pass = retry_test([&](size_t attempt) {
        clp::NetworkReader reader{
            "https://httpbin.org/headers",
            0,
            false,
            clp::CurlDownloadHandler::cDefaultOverallTimeout,
            clp::CurlDownloadHandler::cDefaultConnectionTimeout,
            clp::NetworkReader::cDefaultBufferPoolSize,
            clp::NetworkReader::cDefaultBufferSize,
            illegal_header_kv_pairs
        };
        auto const content = get_content(reader);
        if (!content.empty()) {
            return false;
        }
        if (!assert_curl_error_code(CURLE_BAD_FUNCTION_ARGUMENT, reader)) {
            return false;
        }
        return true;
    });
    REQUIRE(is_pass);
}

By introducing the retry_test helper function, you encapsulate the retry logic, making your tests cleaner and more maintainable. This approach reduces code duplication and simplifies future modifications to the retry mechanism.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment makes sense. Let's add a template helper function to reduce code duplication

Do we still need to do this after I get rid of the retry in illegal header unit test? Now it only retries when conduction regular header test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment makes sense. Let's add a template helper function to reduce code duplication

Do we still need to do this after I get rid of the retry in illegal header unit test? Now it only retries when conduction regular header test.

Sure, let's use the current implementation for now

Comment on lines 257 to 279
bool is_pass{false};
for (size_t i{0}; i < cNumMaxTrials; ++i) {
clp::NetworkReader reader{
"https://httpbin.org/headers",
0,
false,
clp::CurlDownloadHandler::cDefaultOverallTimeout,
clp::CurlDownloadHandler::cDefaultConnectionTimeout,
clp::NetworkReader::cDefaultBufferPoolSize,
clp::NetworkReader::cDefaultBufferSize,
illegal_header_kv_pairs
};
auto const content = get_content(reader);
if (false == content.empty()) {
continue;
}
if (false == assert_curl_error_code(CURLE_BAD_FUNCTION_ARGUMENT, reader)) {
continue;
}
is_pass = true;
break;
}
REQUIRE(is_pass);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment makes sense. Let's add a template helper function to reduce code duplication

break;
}
}
if (!are_headers_same) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!are_headers_same) {
if (false == are_headers_same) {

@@ -193,6 +193,9 @@ TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") {
}

TEST_CASE("network_reader_with_valid_http_header_kv_pairs", "[NetworkReader]") {
// Since the http request sometimes crash due to arbitrary reasons, so we just try it
// multiple times to avoid such cases
constexpr int cNumMaxTrials{10};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
constexpr int cNumMaxTrials{10};
constexpr size_t cNumMaxTrials{10};

Avoid using int for counter like variables

Comment on lines 196 to 197
// Since the http request sometimes crash due to arbitrary reasons, so we just try it
// multiple times to avoid such cases
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Since the http request sometimes crash due to arbitrary reasons, so we just try it
// multiple times to avoid such cases
// Retry the unit test a limited number of times to handle transient server-side HTTP errors.
// This ensures the test is not marked as failed due to temporary issues beyond our control.

Comment on lines 229 to 232
if (value != headers.at(key).get<std::string_view>()) {
are_headers_same = false;
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should assert the returned headers are the same if we get a valid response from the server right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think don't need to asset here:

  • If not the same, it breaks the for loop then are_headers_same will be false, then continue to the next trial
  • If using assert here, then there won't be next trial any more, it will exit due to assertion failure exception

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But exiting due to assertion failure should be the expected behaviour in this case.
Adding this retry is to avoid server-end errors, which is caught in the previous continue already.
When the control flow reaches here, the server's response should be successful. If then we have unmatched headers, it's no longer the server's network issue, and we shouldn't swallow this mismatch.

Comment on lines 270 to 272
if (false == content.empty()) {
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to retry for this unit test, right?
The header validation happens before sending HTTP requests. This unit test should always work regardless of server-end failures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, right, will get rid of it

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
components/core/tests/test-NetworkReader.cpp (1)

209-220: Add exponential backoff between retry attempts.

Consider adding a delay between retries to prevent overwhelming the server and improve test reliability.

 for (size_t i{0}; i < cNumMaxTrials; ++i) {
+    if (i > 0) {
+        // Exponential backoff: 100ms, 200ms, 400ms, etc.
+        std::this_thread::sleep_for(std::chrono::milliseconds{100 * (1 << i)});
+    }
     clp::NetworkReader reader{
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2bde441 and 5ce1ebf.

📒 Files selected for processing (1)
  • components/core/tests/test-NetworkReader.cpp (1 hunks)
🧰 Additional context used
📓 Learnings (1)
components/core/tests/test-NetworkReader.cpp (1)
Learnt from: LinZhihao-723
PR: y-scope/clp#593
File: components/core/tests/test-NetworkReader.cpp:216-219
Timestamp: 2024-11-15T03:15:45.919Z
Learning: In the `network_reader_with_valid_http_header_kv_pairs` test case in `components/core/tests/test-NetworkReader.cpp`, additional error handling for JSON parsing failures is not necessary, as the current error message is considered sufficient.
🔇 Additional comments (1)
components/core/tests/test-NetworkReader.cpp (1)

196-198: LGTM! Clear documentation and proper type usage.

The comments effectively explain the retry mechanism's purpose, and using size_t for the trial counter follows best practices.

Comment on lines 221 to 233
auto const content{get_content(reader)};
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
auto const parsed_content = nlohmann::json::parse(content);
auto const& headers{parsed_content.at("headers")};
for (auto const& [key, value] : valid_http_header_kv_pairs) {
is_pass = value == headers.at(key).get<std::string_view>();
REQUIRE(is_pass);
}
break;
}
REQUIRE(is_pass);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve header validation logic to handle all cases correctly.

The current implementation has several issues:

  1. The is_pass flag is overwritten in each iteration of the header validation loop
  2. The REQUIRE inside the loop makes the final REQUIRE(is_pass) redundant
  3. A failure in any header validation will exit the test immediately, making the retry mechanism ineffective for header mismatches
-        for (auto const& [key, value] : valid_http_header_kv_pairs) {
-            is_pass = value == headers.at(key).get<std::string_view>();
-            REQUIRE(is_pass);
-        }
-        break;
-    }
-    REQUIRE(is_pass);
+        bool are_headers_same{true};
+        for (auto const& [key, value] : valid_http_header_kv_pairs) {
+            if (value != headers.at(key).get<std::string_view>()) {
+                are_headers_same = false;
+                break;
+            }
+        }
+        if (are_headers_same) {
+            is_pass = true;
+            break;
+        }
+    }
+    REQUIRE(is_pass);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto const content{get_content(reader)};
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
auto const parsed_content = nlohmann::json::parse(content);
auto const& headers{parsed_content.at("headers")};
for (auto const& [key, value] : valid_http_header_kv_pairs) {
is_pass = value == headers.at(key).get<std::string_view>();
REQUIRE(is_pass);
}
break;
}
REQUIRE(is_pass);
auto const content{get_content(reader)};
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
auto const parsed_content = nlohmann::json::parse(content);
auto const& headers{parsed_content.at("headers")};
bool are_headers_same{true};
for (auto const& [key, value] : valid_http_header_kv_pairs) {
if (value != headers.at(key).get<std::string_view>()) {
are_headers_same = false;
break;
}
}
if (are_headers_same) {
is_pass = true;
break;
}
}
REQUIRE(is_pass);

Comment on lines 225 to 230
auto const parsed_content = nlohmann::json::parse(content);
auto const& headers{parsed_content.at("headers")};
for (auto const& [key, value] : valid_http_header_kv_pairs) {
is_pass = value == headers.at(key).get<std::string_view>();
REQUIRE(is_pass);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part doesn't have to be in the loop, right? We can just iterate cNumMaxTrials times until get_content has returned with assert_curl_error_code successful. I would suggest the following code structure:

  • Set an optional content to be std::nullopt
  • Loop cNumMaxTrials times and try to read content; if success, set the optional content to the actual read content and break
  • Assert the content is not std::nullopt
  • Assert the headers in the response matches expected ones

@@ -193,33 +193,44 @@ TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") {
}

TEST_CASE("network_reader_with_valid_http_header_kv_pairs", "[NetworkReader]") {
// Retry the unit test a limited number of times to handle transient server-side HTTP errors.
// This ensures the test is not marked as failed due to temporary issues beyond our control.
constexpr size_t cNumMaxTrials{10};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving right before the loop starts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plan on this comment?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify: we should try to make variable declarations as close to where it's been used as possible

Comment on lines 221 to 225
content.emplace(get_content(reader));
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
content.emplace(get_content(reader));
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
break;
if (assert_curl_error_code(CURLE_OK, reader)) {
content.emplace(get_content(reader));
break;
}
  • content.emplace(get_content(reader)) should only execute if the return code is correct, otherwise the coming assertions on content.has_value() is meaningless.
  • I think this if condition will make the code cleaner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I believe that it must call get_content first, otherwise the code in the reader won’t be set right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but you can do this:

Suggested change
content.emplace(get_content(reader));
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
break;
auto content = get_content(reader);
if (assert_curl_error_code(CURLE_OK, reader)) {
optional_content.emplace(std::move(content));
break;
}

@@ -193,33 +193,44 @@ TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") {
}

TEST_CASE("network_reader_with_valid_http_header_kv_pairs", "[NetworkReader]") {
// Retry the unit test a limited number of times to handle transient server-side HTTP errors.
// This ensures the test is not marked as failed due to temporary issues beyond our control.
constexpr size_t cNumMaxTrials{10};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plan on this comment?

auto const content{get_content(reader)};
REQUIRE(assert_curl_error_code(CURLE_OK, reader));
auto const parsed_content = nlohmann::json::parse(content);
std::optional<std::vector<char>> content{std::nullopt};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::optional<std::vector<char>> content{std::nullopt};
std::optional<std::vector<char>> content;

We can rely on the default constructor if the template provides any

Copy link
Member

@LinZhihao-723 LinZhihao-723 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reply this one since it's been ignored for three rounds of conversation: https://github.com/y-scope/clp/pull/613/files#r1861447753

auto const content{get_content(reader)};
REQUIRE(assert_curl_error_code(CURLE_OK, reader));
auto const parsed_content = nlohmann::json::parse(content);
std::optional<std::vector<char>> content;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::optional<std::vector<char>> content;
std::optional<std::vector<char>> optional_content;

Missed in the previous review, mb

Comment on lines 221 to 225
content.emplace(get_content(reader));
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but you can do this:

Suggested change
content.emplace(get_content(reader));
if (false == assert_curl_error_code(CURLE_OK, reader)) {
continue;
}
break;
auto content = get_content(reader);
if (assert_curl_error_code(CURLE_OK, reader)) {
optional_content.emplace(std::move(content));
break;
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3b58ab6 and ff30065.

📒 Files selected for processing (1)
  • components/core/tests/test-NetworkReader.cpp (1 hunks)
🧰 Additional context used
📓 Learnings (1)
components/core/tests/test-NetworkReader.cpp (1)
Learnt from: LinZhihao-723
PR: y-scope/clp#593
File: components/core/tests/test-NetworkReader.cpp:216-219
Timestamp: 2024-11-15T03:15:45.919Z
Learning: In the `network_reader_with_valid_http_header_kv_pairs` test case in `components/core/tests/test-NetworkReader.cpp`, additional error handling for JSON parsing failures is not necessary, as the current error message is considered sufficient.
🔇 Additional comments (3)
components/core/tests/test-NetworkReader.cpp (3)

199-199: LGTM! Appropriate type for counter variable.

The change from int to size_t is correct as it better represents a non-negative counter variable.


206-226: LGTM! Well-implemented retry logic.

The retry logic is well-implemented with:

  • Clear use of optional to track content
  • Proper error handling for transient server errors
  • Good separation of content retrieval and error checking

Line range hint 227-232: LGTM! Robust header validation.

The header validation is properly implemented with:

  • Content existence check
  • Proper JSON parsing
  • Thorough validation of all header key-value pairs
  • Appropriate use of REQUIRE for assertions

std::optional<std::vector<char>> optional_content;
// Retry the unit test a limited number of times to handle transient server-side HTTP errors.
// This ensures the test is not marked as failed due to temporary issues beyond our control.
constexper size_t cNumMaxTrials{10};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in keyword.

There's a typo in the keyword: constexper should be constexpr.

-    constexper size_t cNumMaxTrials{10};
+    constexpr size_t cNumMaxTrials{10};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constexper size_t cNumMaxTrials{10};
constexpr size_t cNumMaxTrials{10};

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
components/core/tests/test-NetworkReader.cpp (1)

206-226: LGTM: Well-structured retry logic

The retry mechanism is well-implemented with clear comments explaining its purpose. Consider adding debug logs for failed attempts to help with troubleshooting.

 if (assert_curl_error_code(CURLE_OK, reader)) {
     optional_content.emplace(content);
     break;
+} else {
+    WARN(fmt::format("Attempt {} failed", i + 1));
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ff30065 and 6d43955.

📒 Files selected for processing (1)
  • components/core/tests/test-NetworkReader.cpp (1 hunks)
🧰 Additional context used
📓 Learnings (1)
components/core/tests/test-NetworkReader.cpp (1)
Learnt from: LinZhihao-723
PR: y-scope/clp#593
File: components/core/tests/test-NetworkReader.cpp:216-219
Timestamp: 2024-11-15T03:15:45.919Z
Learning: In the `network_reader_with_valid_http_header_kv_pairs` test case in `components/core/tests/test-NetworkReader.cpp`, additional error handling for JSON parsing failures is not necessary, as the current error message is considered sufficient.
🔇 Additional comments (2)
components/core/tests/test-NetworkReader.cpp (2)

199-199: LGTM: Appropriate type for container size

The change from int to size_t is correct as it's used for container size and loop counter.


Line range hint 227-232: LGTM: Thorough header validation

The header validation is implemented correctly, with proper assertions to ensure each header matches the expected value.

Copy link
Member

@LinZhihao-723 LinZhihao-723 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the PR title, how about:

test(core): Add support for retrying requests in `network_reader_with_valid_http_header_kv_pairs` on read failures to mitigate server-side errors.

Also, can u update the PR description to align with the latest code changes?

@anlowee anlowee merged commit 12cdf6d into y-scope:main Nov 28, 2024
20 checks passed
@anlowee anlowee deleted the xiaochong-robuster-networkreader-unittest branch November 28, 2024 16:52
@anlowee anlowee changed the title test: Allow multiple trials when unittesting http headers test(core): Allow multiple trials when unittesting http headers Nov 28, 2024
@anlowee anlowee changed the title test(core): Allow multiple trials when unittesting http headers test(core): Add support for retrying requests in network_reader_with_valid_http_header_kv_pairs on read failures to mitigate server-side errors. Nov 28, 2024
jackluo923 pushed a commit to jackluo923/clp that referenced this pull request Dec 4, 2024
davidlion pushed a commit to Bill-hbrhbr/clp that referenced this pull request Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants