Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion contract-tests/sse-contract-tests/include/entity_manager.hpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
#pragma once

#include <launchdarkly/logging/logger.hpp>

Check failure on line 3 in contract-tests/sse-contract-tests/include/entity_manager.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/include/entity_manager.hpp:3:10 [clang-diagnostic-error]

'launchdarkly/logging/logger.hpp' file not found
#include "definitions.hpp"

#include <launchdarkly/sse/client.hpp>

#include <boost/asio/any_io_executor.hpp>

#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <tuple>
#include <unordered_map>

class EventOutbox;

class EntityManager {
using Inbox = std::shared_ptr<launchdarkly::sse::Client>;
using Outbox = std::shared_ptr<EventOutbox>;
using Entity = std::pair<Inbox, Outbox>;
// Third element: suppress-next-error flag for caller-triggered restarts
// (the canceled read surfaces as an error on the read path).
using Entity = std::tuple<Inbox, Outbox, std::shared_ptr<std::atomic_bool>>;

std::unordered_map<std::string, Entity> entities_;

Expand Down Expand Up @@ -49,4 +53,15 @@
* @return True if the entity was found and destroyed.
*/
bool destroy(std::string const& id);
/**
* Send a command to an entity (see the SSE contract test protocol).
* Currently supports "restart", which makes the client disconnect and
* reconnect immediately.
* @param id ID of the entity.
* @param command The command name.
* @return nullopt if no such entity exists; otherwise true if the
* command was recognized.
*/
std::optional<bool> command(std::string const& id,
std::string const& command);
};
38 changes: 32 additions & 6 deletions contract-tests/sse-contract-tests/src/entity_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include "entity_manager.hpp"

Check failure on line 1 in contract-tests/sse-contract-tests/src/entity_manager.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/src/entity_manager.cpp:1:10 [clang-diagnostic-error]

'entity_manager.hpp' file not found
#include "event_outbox.hpp"

Expand Down Expand Up @@ -44,13 +44,23 @@
LD_LOG(logger_, LogLevel::kDebug) << std::move(msg);
});

auto suppress_next_error = std::make_shared<std::atomic_bool>(false);

client_builder.receiver([copy = poster](launchdarkly::sse::Event event) {
copy->post_event(std::move(event));
});

client_builder.errors([copy = poster](launchdarkly::sse::Error event) {
copy->post_error(event);
});
client_builder.errors(
[copy = poster, suppress = suppress_next_error](launchdarkly::sse::Error event) {
// A caller-triggered restart surfaces as an error on the read
// path (the canceled read looks like a body timeout); like other
// services (e.g. okhttp's StreamClosedByCallerException
// suppression) we don't report it to the harness.
if (suppress->exchange(false)) {
return;
}
copy->post_error(event);
});

auto client = client_builder.build();
if (!client) {
Expand All @@ -61,18 +71,34 @@

client->async_connect();

entities_.emplace(id, std::make_pair(client, poster));
entities_.emplace(id, std::make_tuple(client, poster, suppress_next_error));
return id;
}

std::optional<bool> EntityManager::command(std::string const& id,
std::string const& command) {
auto it = entities_.find(id);
if (it == entities_.end()) {
return std::nullopt;
}

if (command == "restart") {
std::get<2>(it->second)->store(true);
std::get<0>(it->second)->async_restart("test harness restart command");
return true;
}

return false;
}

bool EntityManager::destroy(std::string const& id) {
auto it = entities_.find(id);
if (it == entities_.end()) {
return false;
}

it->second.first->async_shutdown(nullptr);
it->second.second->stop();
std::get<0>(it->second)->async_shutdown(nullptr);
std::get<1>(it->second)->stop();

entities_.erase(it);

Expand Down
3 changes: 2 additions & 1 deletion contract-tests/sse-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "server.hpp"

Check failure on line 1 in contract-tests/sse-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/src/main.cpp:1:10 [clang-diagnostic-error]

'server.hpp' file not found

#include <launchdarkly/logging/console_backend.hpp>

Expand All @@ -17,7 +17,7 @@
using launchdarkly::LogLevel;

int main(int argc, char* argv[]) {
launchdarkly::Logger logger{

Check warning on line 20 in contract-tests/sse-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/src/main.cpp:20:26 [cppcoreguidelines-init-variables]

variable 'logger' is not initialized
std::make_unique<ConsoleBackend>("sse-contract-tests")};

std::string const default_port = "8123";
Expand All @@ -30,14 +30,15 @@
try {
net::io_context ioc{1};

server srv(ioc, "0.0.0.0", boost::lexical_cast<unsigned short>(port),

Check warning on line 33 in contract-tests/sse-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/src/main.cpp:33:16 [cppcoreguidelines-init-variables]

variable 'srv' is not initialized
logger);

srv.add_capability("headers");
srv.add_capability("comments");
srv.add_capability("report");
srv.add_capability("post");
srv.add_capability("reconnection");
srv.add_capability("restart");
srv.add_capability("server-directed-shutdown-request");
srv.add_capability("read-timeout");

net::signal_set signals{ioc, SIGINT, SIGTERM};
Expand Down
27 changes: 27 additions & 0 deletions contract-tests/sse-contract-tests/src/session.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "session.hpp"

Check failure on line 1 in contract-tests/sse-contract-tests/src/session.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/sse-contract-tests/src/session.cpp:1:10 [clang-diagnostic-error]

'session.hpp' file not found
#include <boost/algorithm/string/erase.hpp>
#include <boost/asio/dispatch.hpp>
#include <iostream>
Expand Down Expand Up @@ -101,6 +101,33 @@
}
}

if (req.method() == http::verb::post &&
req.target().starts_with(kEntityPath)) {
std::string entity_id = req.target();
boost::erase_first(entity_id, kEntityPath);

std::string command;
try {
auto json = nlohmann::json::parse(req.body());
command = json.at("command").get<std::string>();
} catch (nlohmann::json::exception& e) {
return bad_request("unable to parse command JSON");
}

auto handled = manager_.command(entity_id, command);
if (!handled) {
return not_found(req.target());
}
if (!*handled) {
return bad_request("unrecognized command");
}

Response res{http::status::ok, req.version()};
res.keep_alive(req.keep_alive());
res.prepare_payload();
return res;
}

if (req.method() == http::verb::delete_ &&
req.target().starts_with(kEntityPath)) {
std::string entity_id = req.target();
Expand Down
Loading