Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ jobs:
tidy-checks: '' # Read .clang-tidy for configuration
database: build/Release/compile_commands.json
version: 12


- name: Run linters on tests
continue-on-error: true
Expand All @@ -451,6 +452,7 @@ jobs:
database: build/Release/compile_commands.json
version: 12


- name: Report lint failure
if: steps.source-linter.outputs.checks-failed > 0 || steps.test-linter.outputs.checks-failed > 0
run: |
Expand Down
22 changes: 22 additions & 0 deletions include/up-cpp/utils/CallbackConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
};

/// @brief Semi-private constructor. Use the static establish() instead.
Connection(std::shared_ptr<Callback> cb, PrivateConstructToken)

Check warning on line 196 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:196:64 [readability-named-parameter]

all parameters should be named in a function
: callback_(cb) {}

// Connection is only ever available wrapped in a std::shared_ptr.
Expand Down Expand Up @@ -230,10 +230,22 @@
/// reason.
struct BadConnection : public std::runtime_error {
template <typename... Args>
BadConnection(Args&&... args)

Check warning on line 233 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:233:2 [google-explicit-constructor]

constructors that are callable with a single argument must be marked explicit to avoid unintentional implicit conversions
: std::runtime_error(std::forward<Args>(args)...) {}
};

/// @brief Thrown if an empty std::function parameter was received
///
/// A std::function can be empty. When an empty function is invoked, it will
/// throw std::bad_function_call. We can check earlier by casting the function
/// to a boolean. If the check fails, EmptyFunctionObject is thrown. This makes
/// the error appear earlier without waiting for invocation to occur.
struct EmptyFunctionObject : public std::invalid_argument {
template <typename... Args>
EmptyFunctionObject(Args&&... args)

Check warning on line 245 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:245:2 [google-explicit-constructor]

constructors that are callable with a single argument must be marked explicit to avoid unintentional implicit conversions
: std::invalid_argument(std::forward<Args>(args)...) {}
};

template <typename RT, typename... Args>
struct [[nodiscard]] CalleeHandle {
using Conn = Connection<RT, Args...>;
Expand All @@ -245,7 +257,7 @@
CalleeHandle(std::shared_ptr<Conn> connection,
std::shared_ptr<typename Conn::Callback> callback,
std::optional<typename Conn::Cleanup>&& cleanup,
typename Conn::PrivateConstructToken)

Check warning on line 260 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:260:51 [readability-named-parameter]

all parameters should be named in a function
: connection_(connection),
callback_(callback),
cleanup_(std::move(cleanup)) {
Expand All @@ -254,11 +266,21 @@
"Attempted to create a connected CalleeHandle with bad "
"connection pointer");
}

if (!callback_) {
throw BadConnection(
"Attempted to create a connected CalleeHandle with bad "
"callback pointer");
}

const auto& callback_obj = *callback_;
if (!callback_obj) {
throw EmptyFunctionObject("Callback function is empty");
}

if (cleanup_ && !cleanup_.value()) {
throw EmptyFunctionObject("Cleanup function is empty");
}
}

/// @brief CalleeHandles can be move constructed
Expand Down Expand Up @@ -311,7 +333,7 @@
/// * False if the connection has been broken (i.e. This handle has
/// been reset/moved, or all other references to the connection
/// have been discarded)
bool isConnected() const {

Check warning on line 336 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:336:2 [modernize-use-nodiscard]

function 'isConnected' should be marked [[nodiscard]]
auto locked_connection = connection_.lock();
return locked_connection && (*locked_connection);
}
Expand All @@ -329,7 +351,7 @@
/// CallerHandle that needs to be corrected.
struct BadCallerAccess : public std::logic_error {
template <typename... Args>
BadCallerAccess(Args&&... args)

Check warning on line 354 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:354:2 [google-explicit-constructor]

constructors that are callable with a single argument must be marked explicit to avoid unintentional implicit conversions
: std::logic_error(std::forward<Args>(args)...) {}
};

Expand All @@ -342,7 +364,7 @@

/// @brief Creates a connected handle. Only usable by Connection
CallerHandle(std::shared_ptr<Conn> connection,
typename Conn::PrivateConstructToken)

Check warning on line 367 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:367:51 [readability-named-parameter]

all parameters should be named in a function
: connection_(connection) {
if (!connection_) {
throw BadConnection(
Expand All @@ -368,7 +390,7 @@
/// * False if the connection has been broken (i.e. This handle has
/// been reset/moved, or all other references to the connection
/// have been discarded)
bool isConnected() const { return connection_ && (*connection_); }

Check warning on line 393 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:393:2 [modernize-use-nodiscard]

function 'isConnected' should be marked [[nodiscard]]

/// @throws BadCallerAccess if this handle has been default constructed OR
/// reset() has left it without a valid conneciton pointer.
Expand Down Expand Up @@ -418,7 +440,7 @@
value_ = std::move(v);
return *this;
}
operator std::optional<RT>&&() && { return std::move(value_); }

Check warning on line 443 in include/up-cpp/utils/CallbackConnection.h

View workflow job for this annotation

GitHub Actions / Lint C++ sources

include/up-cpp/utils/CallbackConnection.h:443:2 [google-explicit-constructor]

'operator optional<type-parameter-0-0> &&' must be marked explicit to avoid unintentional implicit conversions

private:
std::optional<RT> value_;
Expand Down
23 changes: 16 additions & 7 deletions test/coverage/communication/NotificationSinkTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

namespace {
using MsgDiff = google::protobuf::util::MessageDifferencer;
using namespace uprotocol::communication;

Check warning on line 24 in test/coverage/communication/NotificationSinkTest.cpp

View workflow job for this annotation

GitHub Actions / Lint C++ sources

test/coverage/communication/NotificationSinkTest.cpp:24:1 [google-build-using-namespace]

do not use namespace using-directives; use using-declarations instead
namespace UriValidator = uprotocol::datamodel::validator::uri;

class NotificationSinkTest : public testing::Test {
Expand Down Expand Up @@ -96,7 +96,7 @@
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[rand() % max_index];

Check failure on line 99 in test/coverage/communication/NotificationSinkTest.cpp

View workflow job for this annotation

GitHub Actions / Lint C++ sources

test/coverage/communication/NotificationSinkTest.cpp:99:18 [clang-diagnostic-sign-conversion]

implicit conversion changes signedness: 'int' to 'unsigned long'
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
Expand Down Expand Up @@ -216,14 +216,23 @@
testDefaultSourceUUri_);

// bind to null callback
auto result = NotificationSink::create(transport, transport->getEntityUri(),
std::move(nullptr), testTopicUUri_);
auto test_create_nullptr = [transport, this]() {
std::ignore =
NotificationSink::create(transport, transport->getEntityUri(),
std::move(nullptr), testTopicUUri_);
};

using namespace uprotocol::utils;

EXPECT_THROW(test_create_nullptr(), callbacks::EmptyFunctionObject);

// Default construct a function object
auto test_create_empty = [transport, this]() {
std::ignore = NotificationSink::create(
transport, transport->getEntityUri(), {}, testTopicUUri_);
};

uprotocol::v1::UMessage msg;
auto attr = std::make_shared<uprotocol::v1::UAttributes>();
*msg.mutable_attributes() = *attr;
msg.set_payload(get_random_string(1400));
EXPECT_THROW(transport->mockMessage(msg), std::bad_function_call);
EXPECT_THROW(test_create_empty(), callbacks::EmptyFunctionObject);
}

} // namespace
21 changes: 14 additions & 7 deletions test/coverage/communication/SubscriberTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[rand() % max_index];

Check failure on line 93 in test/coverage/communication/SubscriberTest.cpp

View workflow job for this annotation

GitHub Actions / Lint C++ sources

test/coverage/communication/SubscriberTest.cpp:93:18 [clang-diagnostic-sign-conversion]

implicit conversion changes signedness: 'int' to 'unsigned long'
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
Expand Down Expand Up @@ -186,14 +186,21 @@
testDefaultSourceUUri_);

// bind to null callback
auto result =
Subscriber::subscribe(transport, testTopicUUri_, std::move(nullptr));
auto test_subscribe_nullptr = [transport, this]() {
std::ignore = Subscriber::subscribe(transport, testTopicUUri_,
std::move(nullptr));
};

using namespace uprotocol::utils;

EXPECT_THROW(test_subscribe_nullptr(), callbacks::EmptyFunctionObject);

// Default construct a function object
auto test_subscribe_empty = [transport, this]() {
std::ignore = Subscriber::subscribe(transport, testTopicUUri_, {});
};

uprotocol::v1::UMessage msg;
auto attr = std::make_shared<uprotocol::v1::UAttributes>();
*msg.mutable_attributes() = *attr;
msg.set_payload(get_random_string(1400));
EXPECT_THROW(transport->mockMessage(msg), std::bad_function_call);
EXPECT_THROW(test_subscribe_empty(), callbacks::EmptyFunctionObject);
}

} // namespace
69 changes: 69 additions & 0 deletions test/coverage/utils/CallbackConnectionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,11 @@
auto [handle, callable] = callbacks::Connection<bool>::establish(
[&fake_blocking_op]() { return fake_blocking_op.try_acquire_for(1s); });

auto caller_fn = ([callable, &callbacks_pending, &callbacks_released,

Check failure on line 589 in test/coverage/utils/CallbackConnectionTest.cpp

View workflow job for this annotation

GitHub Actions / Lint C++ sources

test/coverage/utils/CallbackConnectionTest.cpp:589:21 [clang-diagnostic-error]

'callable' in capture list does not name a variable
&main_task_sync]() mutable {
++callbacks_pending;
main_task_sync.release();
auto did_not_expire = callable();

Check failure on line 593 in test/coverage/utils/CallbackConnectionTest.cpp

View workflow job for this annotation

GitHub Actions / Lint C++ sources

test/coverage/utils/CallbackConnectionTest.cpp:593:25 [clang-diagnostic-error]

reference to local binding 'callable' declared in enclosing function '(anonymous namespace)::CallbackTest_HandleResetBlocksWhileCallbacksRunning_Test::TestBody'
--callbacks_pending;
if (did_not_expire) {
++callbacks_released;
Expand Down Expand Up @@ -698,4 +698,73 @@
});
}

///////////////////////////////////////////////////////////////////////////////
// It is possible to create std::function objects with no target function. When
// they are invoked, they throw std::bad_function_call. This is not desireable,
// so the callback connections modules are required to check the validity of
// function objects they receive

// Tests invalid callback function objects
TEST_F(CallbackTest, EstablishWithNonCallableCallback) {
using namespace uprotocol::utils;

callbacks::Connection<bool>::ConnectedPair conn;

EXPECT_THROW(conn = callbacks::Connection<bool>::establish({}),
callbacks::EmptyFunctionObject);

auto& [handle, callable] = conn;

// Ordering is important here. If handle.reset() tries blindly to call the
// cleanup callback, the exception could be thrown before the connection
// is broken. When that happens, the destructor will try to reset again.
// By resetting the callable second, there is no need to try the cleanup
// funciton again, so the destructor won't throw.
EXPECT_NO_THROW(handle.reset());
EXPECT_NO_THROW(callable.reset());
}

// Tests invalid cleanup function objects
TEST_F(CallbackTest, EstablishWithNonCallableCleanup) {
using namespace uprotocol::utils;

auto cb = []() -> bool { return true; };
callbacks::Connection<bool>::Cleanup empty;
callbacks::Connection<bool>::ConnectedPair conn;

EXPECT_THROW(conn = callbacks::Connection<bool>::establish(cb, empty),
callbacks::EmptyFunctionObject);

auto& [handle, callable] = conn;

// Ordering is important here. If handle.reset() tries blindly to call the
// cleanup callback, the exception could be thrown before the connection
// is broken. When that happens, the destructor will try to reset again.
// By resetting the callable second, there is no need to try the cleanup
// funciton again, so the destructor won't throw.
EXPECT_NO_THROW(handle.reset());
EXPECT_NO_THROW(callable.reset());
}

// Tests both invalid cleanup and invalid callback function objects
TEST_F(CallbackTest, EstablishWithNonCallableCallbackAndCleanup) {
using namespace uprotocol::utils;

callbacks::Connection<bool>::Cleanup empty;
callbacks::Connection<bool>::ConnectedPair conn;

EXPECT_THROW(conn = callbacks::Connection<bool>::establish({}, empty),
callbacks::EmptyFunctionObject);

auto& [handle, callable] = conn;

// Ordering is important here. If handle.reset() tries blindly to call the
// cleanup callback, the exception could be thrown before the connection
// is broken. When that happens, the destructor will try to reset again.
// By resetting the callable second, there is no need to try the cleanup
// funciton again, so the destructor won't throw.
EXPECT_NO_THROW(handle.reset());
EXPECT_NO_THROW(callable.reset());
}

} // namespace
Loading