Revert "Revert "New test for tdlib. New Promise/Future features (ported from other project)""
This reverts commit 56866524aa
.
This commit is contained in:
parent
cbca6ca902
commit
ab3e695f94
@ -451,7 +451,7 @@ const CountryInfoManager::CountryList *CountryInfoManager::get_country_list(cons
|
||||
it = countries_.find(language_code);
|
||||
CHECK(it != countries_.end())
|
||||
auto *country = it->second.get();
|
||||
load_country_list(language_code, country->hash, Auto());
|
||||
load_country_list(language_code, country->hash, {});
|
||||
return country;
|
||||
}
|
||||
return nullptr;
|
||||
@ -460,7 +460,7 @@ const CountryInfoManager::CountryList *CountryInfoManager::get_country_list(cons
|
||||
auto *country = it->second.get();
|
||||
CHECK(country != nullptr);
|
||||
if (country->next_reload_time < Time::now()) {
|
||||
load_country_list(language_code, country->hash, Auto());
|
||||
load_country_list(language_code, country->hash, {});
|
||||
}
|
||||
|
||||
return country;
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
template <class T = Unit>
|
||||
class PromiseInterface {
|
||||
public:
|
||||
@ -43,6 +42,15 @@ class PromiseInterface {
|
||||
set_error(result.move_as_error());
|
||||
}
|
||||
}
|
||||
void operator()(T &&value) {
|
||||
set_value(std::move(value));
|
||||
}
|
||||
void operator()(Status &&error) {
|
||||
set_error(std::move(error));
|
||||
}
|
||||
void operator()(Result<T> &&result) {
|
||||
set_result(std::move(result));
|
||||
}
|
||||
virtual bool is_cancellable() const {
|
||||
return false;
|
||||
}
|
||||
@ -56,10 +64,182 @@ class PromiseInterface {
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
struct GetArg : public GetArg<decltype(&T::operator())> {};
|
||||
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg)> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg) const> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
|
||||
|
||||
template <class T>
|
||||
struct DropResult {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct DropResult<Result<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using drop_result_t = typename DropResult<T>::type;
|
||||
|
||||
struct Ignore {
|
||||
void operator()(Status &&error) {
|
||||
error.ignore();
|
||||
}
|
||||
};
|
||||
template <class ValueT, class FunctionOkT, class FunctionFailT = Ignore>
|
||||
class LambdaPromise : public PromiseInterface<ValueT> {
|
||||
enum class OnFail { None, Ok, Fail };
|
||||
|
||||
public:
|
||||
void set_value(ValueT &&value) override {
|
||||
CHECK(has_lambda_.get());
|
||||
do_ok(ok_, std::move(value));
|
||||
on_fail_ = OnFail::None;
|
||||
}
|
||||
void set_error(Status &&error) override {
|
||||
CHECK(has_lambda_.get());
|
||||
do_error(std::move(error));
|
||||
}
|
||||
LambdaPromise(const LambdaPromise &other) = delete;
|
||||
LambdaPromise &operator=(const LambdaPromise &other) = delete;
|
||||
LambdaPromise(LambdaPromise &&other) = default;
|
||||
LambdaPromise &operator=(LambdaPromise &&other) = default;
|
||||
~LambdaPromise() override {
|
||||
if (has_lambda_.get()) {
|
||||
do_error(Status::Error("Lost promise"));
|
||||
}
|
||||
}
|
||||
|
||||
template <class FromOkT, class FromFailT>
|
||||
LambdaPromise(FromOkT &&ok, FromFailT &&fail, bool use_ok_as_fail)
|
||||
: ok_(std::forward<FromOkT>(ok))
|
||||
, fail_(std::forward<FromFailT>(fail))
|
||||
, on_fail_(use_ok_as_fail ? OnFail::Ok : OnFail::Fail), has_lambda_(true) {
|
||||
}
|
||||
template <class FromOkT>
|
||||
LambdaPromise(FromOkT &&ok) : LambdaPromise(std::move(ok), Ignore(), true) {
|
||||
}
|
||||
|
||||
private:
|
||||
FunctionOkT ok_;
|
||||
FunctionFailT fail_;
|
||||
OnFail on_fail_ = OnFail::None;
|
||||
MovableValue<bool> has_lambda_{false};
|
||||
|
||||
void do_error(Status &&error) {
|
||||
switch (on_fail_) {
|
||||
case OnFail::None:
|
||||
break;
|
||||
case OnFail::Ok:
|
||||
do_error(ok_, std::move(error));
|
||||
break;
|
||||
case OnFail::Fail:
|
||||
do_error(fail_, std::move(error));
|
||||
break;
|
||||
}
|
||||
on_fail_ = OnFail::None;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_error(F &&f, Status &&status) {
|
||||
f(Result<ValueT>(std::move(status)));
|
||||
}
|
||||
template <class Y, class F>
|
||||
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_error(F &&f, Y &&status) {
|
||||
f(Auto());
|
||||
}
|
||||
template <class F>
|
||||
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_ok(F &&f, ValueT &&result) {
|
||||
f(Result<ValueT>(std::move(result)));
|
||||
}
|
||||
template <class F >
|
||||
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_ok(F &&f, ValueT &&result) {
|
||||
f(std::move(result));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class SafePromise;
|
||||
|
||||
template <class T = Unit>
|
||||
class Promise;
|
||||
|
||||
constexpr std::false_type is_promise_interface(...) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface(const PromiseInterface<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface(const Promise<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr bool is_promise_interface() {
|
||||
return decltype(is_promise_interface(std::declval<F>()))::value;
|
||||
}
|
||||
|
||||
constexpr std::false_type is_promise_interface_ptr(...) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface_ptr(const unique_ptr<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr bool is_promise_interface_ptr() {
|
||||
return decltype(is_promise_interface_ptr(std::declval<F>()))::value;
|
||||
}
|
||||
template <class T = void, class F = void, std::enable_if_t<std::is_same<T, void>::value, bool> has_t = false>
|
||||
auto lambda_promise(F &&f) {
|
||||
return detail::LambdaPromise<detail::drop_result_t<detail::get_arg_t<std::decay_t<F>>>, std::decay_t<F>>(std::forward<F>(f));
|
||||
}
|
||||
template <class T = void, class F = void, std::enable_if_t<!std::is_same<T, void>::value, bool> has_t = true>
|
||||
auto lambda_promise(F &&f) {
|
||||
return detail::LambdaPromise<T, std::decay_t<F>>(std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<is_promise_interface<F>(), bool> from_promise_inerface = true>
|
||||
auto &&promise_interface(F &&f) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<!is_promise_interface<F>(), bool> from_promise_inerface = false>
|
||||
auto promise_interface(F &&f) {
|
||||
return lambda_promise<T>(std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<is_promise_interface_ptr<F>(), bool> from_promise_inerface = true>
|
||||
auto promise_interface_ptr(F &&f) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
template <class T, class F, std::enable_if_t<!is_promise_interface_ptr<F>(), bool> from_promise_inerface = false>
|
||||
auto promise_interface_ptr(F &&f) {
|
||||
return td::make_unique<std::decay_t<decltype(promise_interface<T>(std::forward<F>(f)))>>(
|
||||
promise_interface<T>(std::forward<F>(f)));
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
class Promise {
|
||||
public:
|
||||
void set_value(T &&value) {
|
||||
@ -83,6 +263,14 @@ class Promise {
|
||||
promise_->set_result(std::move(result));
|
||||
promise_.reset();
|
||||
}
|
||||
template <class S>
|
||||
void operator()(S &&result) {
|
||||
if (!promise_) {
|
||||
return;
|
||||
}
|
||||
promise_->operator()(std::forward<S>(result));
|
||||
promise_.reset();
|
||||
}
|
||||
void reset() {
|
||||
promise_.reset();
|
||||
}
|
||||
@ -117,8 +305,13 @@ class Promise {
|
||||
Promise() = default;
|
||||
explicit Promise(unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
Promise(Auto) {
|
||||
}
|
||||
Promise(SafePromise<T> &&other);
|
||||
Promise &operator=(SafePromise<T> &&other);
|
||||
template <class F>
|
||||
Promise(F &&f) : promise_(promise_interface_ptr<T>(std::forward<F>(f))) {
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return static_cast<bool>(promise_);
|
||||
@ -209,36 +402,6 @@ class EventPromise : public PromiseInterface<Unit> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetArg : public GetArg<decltype(&T::operator())> {};
|
||||
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg)> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg) const> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
|
||||
|
||||
template <class T>
|
||||
struct DropResult {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct DropResult<Result<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using drop_result_t = typename DropResult<T>::type;
|
||||
|
||||
template <class PromiseT>
|
||||
class CancellablePromise : public PromiseT {
|
||||
public:
|
||||
@ -257,62 +420,6 @@ class CancellablePromise : public PromiseT {
|
||||
CancellationToken cancellation_token_;
|
||||
};
|
||||
|
||||
template <class ValueT, class FunctionOkT, class FunctionFailT>
|
||||
class LambdaPromise : public PromiseInterface<ValueT> {
|
||||
enum class OnFail { None, Ok, Fail };
|
||||
|
||||
public:
|
||||
void set_value(ValueT &&value) override {
|
||||
ok_(std::move(value));
|
||||
on_fail_ = OnFail::None;
|
||||
}
|
||||
void set_error(Status &&error) override {
|
||||
do_error(std::move(error));
|
||||
}
|
||||
LambdaPromise(const LambdaPromise &other) = delete;
|
||||
LambdaPromise &operator=(const LambdaPromise &other) = delete;
|
||||
LambdaPromise(LambdaPromise &&other) = delete;
|
||||
LambdaPromise &operator=(LambdaPromise &&other) = delete;
|
||||
~LambdaPromise() override {
|
||||
do_error(Status::Error("Lost promise"));
|
||||
}
|
||||
|
||||
template <class FromOkT, class FromFailT>
|
||||
LambdaPromise(FromOkT &&ok, FromFailT &&fail, bool use_ok_as_fail)
|
||||
: ok_(std::forward<FromOkT>(ok))
|
||||
, fail_(std::forward<FromFailT>(fail))
|
||||
, on_fail_(use_ok_as_fail ? OnFail::Ok : OnFail::Fail) {
|
||||
}
|
||||
|
||||
private:
|
||||
FunctionOkT ok_;
|
||||
FunctionFailT fail_;
|
||||
OnFail on_fail_ = OnFail::None;
|
||||
|
||||
template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
|
||||
std::enable_if_t<std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
|
||||
func(std::move(status));
|
||||
}
|
||||
|
||||
template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
|
||||
std::enable_if_t<!std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
|
||||
func(Auto());
|
||||
}
|
||||
|
||||
void do_error(Status &&error) {
|
||||
switch (on_fail_) {
|
||||
case OnFail::None:
|
||||
break;
|
||||
case OnFail::Ok:
|
||||
do_error_impl(ok_, std::move(error));
|
||||
break;
|
||||
case OnFail::Fail:
|
||||
fail_(std::move(error));
|
||||
break;
|
||||
}
|
||||
on_fail_ = OnFail::None;
|
||||
}
|
||||
};
|
||||
|
||||
template <class... ArgsT>
|
||||
class JoinPromise : public PromiseInterface<Unit> {
|
||||
@ -331,6 +438,30 @@ class JoinPromise : public PromiseInterface<Unit> {
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
class SendClosure {
|
||||
public:
|
||||
template <class... ArgsT>
|
||||
void operator()(ArgsT &&... args) const {
|
||||
send_closure(std::forward<ArgsT>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
//template <class T>
|
||||
//template <class... ArgsT>
|
||||
//auto Promise<T>::send_closure(ArgsT &&... args) {
|
||||
// return [promise = std::move(*this), t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&r_res) mutable {
|
||||
// TRY_RESULT_PROMISE(promise, res, std::move(r_res));
|
||||
// td2::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res), std::move(promise))));
|
||||
// };
|
||||
//}
|
||||
|
||||
template <class... ArgsT>
|
||||
auto promise_send_closure(ArgsT &&... args) {
|
||||
return [t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&res) mutable {
|
||||
call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res))));
|
||||
};
|
||||
}
|
||||
|
||||
/*** FutureActor and PromiseActor ***/
|
||||
template <class T>
|
||||
class FutureActor;
|
||||
@ -559,16 +690,12 @@ FutureActor<T> send_promise(ActorId<ActorAT> actor_id, ResultT (ActorBT::*func)(
|
||||
|
||||
class PromiseCreator {
|
||||
public:
|
||||
struct Ignore {
|
||||
void operator()(Status &&error) {
|
||||
error.ignore();
|
||||
}
|
||||
};
|
||||
using Ignore = detail::Ignore;
|
||||
|
||||
template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
|
||||
static Promise<ArgT> lambda(OkT &&ok) {
|
||||
return Promise<ArgT>(
|
||||
td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>, Ignore>>(std::forward<OkT>(ok), Ignore(), true));
|
||||
td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>(std::forward<OkT>(ok)));
|
||||
}
|
||||
|
||||
template <class OkT, class FailT, class ArgT = detail::get_arg_t<OkT>>
|
||||
@ -580,8 +707,8 @@ class PromiseCreator {
|
||||
template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
|
||||
static auto cancellable_lambda(CancellationToken cancellation_token, OkT &&ok) {
|
||||
return Promise<ArgT>(
|
||||
td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>, Ignore>>>(
|
||||
std::move(cancellation_token), std::forward<OkT>(ok), Ignore(), true));
|
||||
td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>>(
|
||||
std::move(cancellation_token), std::forward<OkT>(ok)));
|
||||
}
|
||||
|
||||
static Promise<> event(EventFull &&ok) {
|
||||
|
@ -33,16 +33,19 @@ set(TESTS_MAIN
|
||||
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
|
||||
#Tests
|
||||
add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
|
||||
add_executable(test-online EXCLUDE_FROM_ALL online.cpp)
|
||||
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
|
||||
if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_HOST_SYSTEM_NAME MATCHES "OpenBSD"))
|
||||
target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
|
||||
target_compile_options(run_all_tests PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
|
||||
target_compile_options(test-online PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr")
|
||||
endif()
|
||||
target_include_directories(run_all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_include_directories(test-tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(test-tdutils PRIVATE tdutils)
|
||||
target_link_libraries(run_all_tests PRIVATE tdcore tdclient)
|
||||
target_link_libraries(test-online PRIVATE tdcore tdclient tdutils tdactor)
|
||||
|
||||
if (CLANG)
|
||||
# add_executable(fuzz_url fuzz_url.cpp)
|
||||
|
622
test/online.cpp
Normal file
622
test/online.cpp
Normal file
@ -0,0 +1,622 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <tdutils/td/utils/FileLog.h>
|
||||
#include <tdutils/td/utils/OptionParser.h>
|
||||
#include <tdutils/td/utils/port/path.h>
|
||||
#include <td/telegram/ClientActor.h>
|
||||
#include <tdutils/td/utils/filesystem.h>
|
||||
#include "td/telegram/TdCallback.h"
|
||||
#include "td/utils/port/signals.h"
|
||||
#include "td/telegram/Log.h"
|
||||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
#include "td/actor/MultiPromise.h"
|
||||
#include "td/telegram/td_api_json.h"
|
||||
|
||||
namespace td {
|
||||
template <class T>
|
||||
static void check_td_error(T &result) {
|
||||
LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
|
||||
}
|
||||
|
||||
class TestClient : public Actor {
|
||||
public:
|
||||
explicit TestClient(td::string name) : name_(std::move(name)) {
|
||||
}
|
||||
struct Update {
|
||||
td::uint64 id;
|
||||
td::tl_object_ptr<td::td_api::Object> object;
|
||||
Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
|
||||
}
|
||||
};
|
||||
class Listener {
|
||||
public:
|
||||
Listener() = default;
|
||||
Listener(const Listener &) = delete;
|
||||
Listener &operator=(const Listener &) = delete;
|
||||
Listener(Listener &&) = delete;
|
||||
Listener &operator=(Listener &&) = delete;
|
||||
virtual ~Listener() = default;
|
||||
virtual void start_listen(TestClient *client) {
|
||||
}
|
||||
virtual void stop_listen() {
|
||||
}
|
||||
virtual void on_update(std::shared_ptr<Update> update) = 0;
|
||||
};
|
||||
struct RemoveListener {
|
||||
void operator()(Listener *listener) {
|
||||
send_closure(self, &TestClient::remove_listener, listener);
|
||||
}
|
||||
ActorId<TestClient> self;
|
||||
};
|
||||
using ListenerToken = std::unique_ptr<Listener, RemoveListener>;
|
||||
void close(td::Promise<> close_promise) {
|
||||
close_promise_ = std::move(close_promise);
|
||||
td_client_.reset();
|
||||
}
|
||||
|
||||
td::unique_ptr<td::TdCallback> make_td_callback() {
|
||||
class TdCallbackImpl : public td::TdCallback {
|
||||
public:
|
||||
explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
|
||||
}
|
||||
void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) override {
|
||||
send_closure(client_, &TestClient::on_result, id, std::move(result));
|
||||
}
|
||||
void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) override {
|
||||
send_closure(client_, &TestClient::on_error, id, std::move(error));
|
||||
}
|
||||
TdCallbackImpl(const TdCallbackImpl &) = delete;
|
||||
TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
|
||||
TdCallbackImpl(TdCallbackImpl &&) = delete;
|
||||
TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
|
||||
~TdCallbackImpl() override {
|
||||
send_closure(client_, &TestClient::on_closed);
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorId<TestClient> client_;
|
||||
};
|
||||
return td::make_unique<TdCallbackImpl>(actor_id(this));
|
||||
}
|
||||
|
||||
void add_listener(td::unique_ptr<Listener> listener) {
|
||||
auto *ptr = listener.get();
|
||||
listeners_.push_back(std::move(listener));
|
||||
ptr->start_listen(this);
|
||||
}
|
||||
void remove_listener(Listener *listener) {
|
||||
pending_remove_.push_back(listener);
|
||||
}
|
||||
void do_pending_remove_listeners() {
|
||||
for (auto listener : pending_remove_) {
|
||||
do_remove_listener(listener);
|
||||
}
|
||||
pending_remove_.clear();
|
||||
}
|
||||
void do_remove_listener(Listener *listener) {
|
||||
for (size_t i = 0; i < listeners_.size(); i++) {
|
||||
if (listeners_[i].get() == listener) {
|
||||
listener->stop_listen();
|
||||
listeners_.erase(listeners_.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
|
||||
on_update(std::make_shared<Update>(id, std::move(result)));
|
||||
}
|
||||
void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
|
||||
on_update(std::make_shared<Update>(id, std::move(error)));
|
||||
}
|
||||
void on_update(std::shared_ptr<Update> update) {
|
||||
for (auto &listener : listeners_) {
|
||||
listener->on_update(update);
|
||||
}
|
||||
do_pending_remove_listeners();
|
||||
}
|
||||
|
||||
void on_closed() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void start_up() override {
|
||||
auto old_context = set_context(std::make_shared<td::ActorContext>());
|
||||
set_tag(name_);
|
||||
LOG(INFO) << "START UP!";
|
||||
|
||||
td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
|
||||
}
|
||||
|
||||
td::ActorOwn<td::ClientActor> td_client_;
|
||||
|
||||
td::string name_;
|
||||
|
||||
private:
|
||||
td::vector<td::unique_ptr<Listener>> listeners_;
|
||||
td::vector<Listener *> pending_remove_;
|
||||
|
||||
td::Promise<> close_promise_;
|
||||
};
|
||||
|
||||
class Task : public TestClient::Listener {
|
||||
public:
|
||||
void on_update(std::shared_ptr<TestClient::Update> update) override {
|
||||
auto it = sent_queries_.find(update->id);
|
||||
if (it != sent_queries_.end()) {
|
||||
it->second(std::move(update->object));
|
||||
sent_queries_.erase(it);
|
||||
}
|
||||
process_update(update);
|
||||
}
|
||||
void start_listen(TestClient *client) override {
|
||||
client_ = client;
|
||||
start_up();
|
||||
}
|
||||
virtual void process_update(std::shared_ptr<TestClient::Update> update) {
|
||||
}
|
||||
|
||||
template <class FunctionT, class CallbackT>
|
||||
void send_query(td::tl_object_ptr<FunctionT> function, CallbackT callback) {
|
||||
auto id = current_query_id_++;
|
||||
|
||||
using ResultT = typename FunctionT::ReturnType;
|
||||
sent_queries_[id] =
|
||||
[callback = Promise<ResultT>(std::move(callback))](Result<tl_object_ptr<td_api::Object>> r_obj) mutable {
|
||||
TRY_RESULT_PROMISE(callback, obj, std::move(r_obj));
|
||||
if (obj->get_id() == td::td_api::error::ID) {
|
||||
auto err = move_tl_object_as<td_api::error>(std::move(obj));
|
||||
callback.set_error(Status::Error(err->code_, err->message_));
|
||||
return;
|
||||
}
|
||||
callback.set_value(move_tl_object_as<typename ResultT::element_type>(std::move(obj)));
|
||||
};
|
||||
send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
|
||||
}
|
||||
|
||||
protected:
|
||||
std::map<td::uint64, Promise<td::tl_object_ptr<td::td_api::Object>>> sent_queries_;
|
||||
TestClient *client_ = nullptr;
|
||||
td::uint64 current_query_id_ = 1;
|
||||
|
||||
virtual void start_up() {
|
||||
}
|
||||
void stop() {
|
||||
client_->remove_listener(this);
|
||||
client_ = nullptr;
|
||||
}
|
||||
bool is_alive() const {
|
||||
return client_ != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class InitTask : public Task {
|
||||
public:
|
||||
struct Options {
|
||||
string name;
|
||||
int32 api_id;
|
||||
string api_hash;
|
||||
};
|
||||
InitTask(Options options, td::Promise<> promise) : options_(std::move(options)), promise_(std::move(promise)) {
|
||||
}
|
||||
|
||||
private:
|
||||
Options options_;
|
||||
td::Promise<> promise_;
|
||||
bool start_flag_{false};
|
||||
|
||||
void start_up() override {
|
||||
send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
|
||||
[this](auto res) { this->process_authorization_state(res.move_as_ok()); });
|
||||
}
|
||||
void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
|
||||
start_flag_ = true;
|
||||
td::tl_object_ptr<td::td_api::Function> function;
|
||||
switch (authorization_state->get_id()) {
|
||||
case td::td_api::authorizationStateWaitEncryptionKey::ID:
|
||||
send(td::make_tl_object<td::td_api::checkDatabaseEncryptionKey>());
|
||||
break;
|
||||
case td::td_api::authorizationStateReady::ID:
|
||||
promise_.set_value({});
|
||||
stop();
|
||||
break;
|
||||
case td::td_api::authorizationStateWaitTdlibParameters::ID: {
|
||||
auto parameters = td::td_api::make_object<td::td_api::tdlibParameters>();
|
||||
parameters->use_test_dc_ = true;
|
||||
parameters->database_directory_ = options_.name + TD_DIR_SLASH;
|
||||
parameters->use_message_database_ = true;
|
||||
parameters->use_secret_chats_ = true;
|
||||
parameters->api_id_ = options_.api_id;
|
||||
parameters->api_hash_ = options_.api_hash;
|
||||
parameters->system_language_code_ = "en";
|
||||
parameters->device_model_ = "Desktop";
|
||||
parameters->application_version_ = "tdclient-test";
|
||||
parameters->ignore_file_names_ = false;
|
||||
parameters->enable_storage_optimizer_ = true;
|
||||
send(td::td_api::make_object<td::td_api::setTdlibParameters>(std::move(parameters)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(ERROR) << "???";
|
||||
promise_.set_error(
|
||||
Status::Error(PSLICE() << "Unexpected authorization state " << to_string(authorization_state)));
|
||||
stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
void send(T &&query) {
|
||||
send_query(std::move(query), [this](auto res) {
|
||||
if (is_alive()) {
|
||||
res.ensure();
|
||||
}
|
||||
});
|
||||
}
|
||||
void process_update(std::shared_ptr<TestClient::Update> update) override {
|
||||
if (!start_flag_) {
|
||||
return;
|
||||
}
|
||||
if (!update->object) {
|
||||
return;
|
||||
}
|
||||
if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
|
||||
auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
|
||||
process_authorization_state(std::move(update_authorization_state->authorization_state_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GetMe : public Task {
|
||||
public:
|
||||
struct Result {
|
||||
int32 user_id;
|
||||
int64 chat_id;
|
||||
};
|
||||
GetMe(Promise<Result> promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
void start_up() override {
|
||||
send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { with_user_id(res.move_as_ok()->id_); });
|
||||
}
|
||||
|
||||
private:
|
||||
Promise<Result> promise_;
|
||||
Result result_;
|
||||
|
||||
void with_user_id(int32 user_id) {
|
||||
result_.user_id = user_id;
|
||||
send_query(td::make_tl_object<td::td_api::createPrivateChat>(user_id, false), [this](auto res) { with_chat_id(res.move_as_ok()->id_); });
|
||||
}
|
||||
|
||||
void with_chat_id(int64 chat_id) {
|
||||
result_.chat_id = chat_id;
|
||||
promise_.set_value(std::move(result_));
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
class UploadFile : public Task {
|
||||
public:
|
||||
struct Result {
|
||||
std::string content;
|
||||
std::string remote_id;
|
||||
};
|
||||
UploadFile(std::string dir, std::string content, int64 chat_id, Promise<Result> promise) : dir_(std::move(dir)), content_(std::move(content)), chat_id_(std::move(chat_id)), promise_(std::move(promise)) {
|
||||
}
|
||||
void start_up() override {
|
||||
auto hash = hex_encode(sha256(content_)).substr(0, 10);
|
||||
content_path_ = dir_ + TD_DIR_SLASH + hash + ".data";
|
||||
id_path_ = dir_ + TD_DIR_SLASH + hash + ".id";
|
||||
|
||||
auto r_id = read_file(id_path_);
|
||||
if (r_id.is_ok() && r_id.ok().size() > 10) {
|
||||
auto id = r_id.move_as_ok();
|
||||
LOG(ERROR) << "Got file from cache";
|
||||
Result res;
|
||||
res.content = std::move(content_);
|
||||
res.remote_id = id.as_slice().str();
|
||||
promise_.set_value(std::move(res));
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
write_file(content_path_, content_).ensure();
|
||||
|
||||
send_query(td::make_tl_object<td::td_api::sendMessage>(
|
||||
chat_id_, 0, 0, nullptr, nullptr,
|
||||
td::make_tl_object<td::td_api::inputMessageDocument>(
|
||||
td::make_tl_object<td::td_api::inputFileLocal>(content_path_), nullptr, true,
|
||||
td::make_tl_object<td::td_api::formattedText>("tag", td::Auto()))),
|
||||
[this](auto res) { with_message(res.move_as_ok()); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::string dir_;
|
||||
std::string content_path_;
|
||||
std::string id_path_;
|
||||
std::string content_;
|
||||
int64 chat_id_;
|
||||
Promise<Result> promise_;
|
||||
int64 file_id_{0};
|
||||
|
||||
void with_message(td::tl_object_ptr<td_api::message> message) {
|
||||
CHECK(message->content_->get_id() == td::td_api::messageDocument::ID);
|
||||
auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
|
||||
on_file(*messageDocument->document_->document_, true);
|
||||
}
|
||||
|
||||
void on_file(const td_api::file &file, bool force = false) {
|
||||
if (force) {
|
||||
file_id_ = file.id_;
|
||||
}
|
||||
if (file.id_ != file_id_) {
|
||||
return;
|
||||
}
|
||||
if (file.remote_->is_uploading_completed_) {
|
||||
Result res;
|
||||
res.content = std::move(content_);
|
||||
res.remote_id = file.remote_->id_;
|
||||
|
||||
unlink(content_path_).ignore();
|
||||
atomic_write_file(id_path_, res.remote_id).ignore();
|
||||
|
||||
promise_.set_value(std::move(res));
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void process_update(std::shared_ptr<TestClient::Update> update) override {
|
||||
if (!update->object) {
|
||||
return;
|
||||
}
|
||||
if (update->object->get_id() == td::td_api::updateFile::ID) {
|
||||
auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
|
||||
on_file(*updateFile->file_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TestDownloadFile : public Task {
|
||||
public:
|
||||
TestDownloadFile(std::string remote_id, std::string content, Promise<Unit> promise) : remote_id_(std::move(remote_id)), content_(std::move(content)), promise_(std::move(promise)) {
|
||||
}
|
||||
void start_up() override {
|
||||
send_query(td::make_tl_object<td::td_api::getRemoteFile>(
|
||||
remote_id_, nullptr
|
||||
), [this](auto res) { start_file(*res.ok()); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::string remote_id_;
|
||||
std::string content_;
|
||||
Promise<Unit> promise_;
|
||||
struct Range {
|
||||
size_t begin;
|
||||
size_t end;
|
||||
};
|
||||
int32 file_id_{0};
|
||||
std::vector<Range> ranges_;
|
||||
|
||||
|
||||
void start_file(const td_api::file &file) {
|
||||
LOG(ERROR) << "Start";
|
||||
file_id_ = file.id_;
|
||||
// CHECK(!file.local_->is_downloading_active_);
|
||||
// CHECK(!file.local_->is_downloading_completed_);
|
||||
// CHECK(file.local_->download_offset_ == 0);
|
||||
if (!file.local_->path_.empty()) {
|
||||
unlink(file.local_->path_).ignore();
|
||||
}
|
||||
|
||||
size_t size = file.size_;
|
||||
Random::Xorshift128plus rnd(123);
|
||||
|
||||
size_t begin = 0;
|
||||
|
||||
while (begin + 128u < size) {
|
||||
auto chunk_size = rnd.fast(128, 3096);
|
||||
auto end = begin + chunk_size;
|
||||
if (end > size) {
|
||||
end = size;
|
||||
}
|
||||
|
||||
ranges_.push_back({begin, end});
|
||||
begin = end;
|
||||
}
|
||||
|
||||
random_shuffle(as_mutable_span(ranges_), rnd);
|
||||
start_chunk();
|
||||
}
|
||||
|
||||
void got_chunk(const td_api::file &file) {
|
||||
LOG(ERROR) << "Got chunk";
|
||||
auto range = ranges_.back();
|
||||
std::string got_chunk(range.end - range.begin, '\0');
|
||||
FileFd::open(file.local_->path_, FileFd::Flags::Read).move_as_ok().pread(got_chunk, range.begin).ensure();
|
||||
CHECK(got_chunk == as_slice(content_).substr(range.begin, range.end - range.begin));
|
||||
ranges_.pop_back();
|
||||
if (ranges_.empty()) {
|
||||
promise_.set_value(Unit{});
|
||||
return stop();
|
||||
}
|
||||
start_chunk();
|
||||
}
|
||||
|
||||
void start_chunk() {
|
||||
|
||||
send_query(td::make_tl_object<td::td_api::downloadFile>(
|
||||
file_id_, 1, int(ranges_.back().begin), int(ranges_.back().end - ranges_.back().begin), true
|
||||
), [this](auto res) { got_chunk(*res.ok()); });
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
std::string gen_readable_file(size_t block_size, size_t block_count) {
|
||||
std::string content;
|
||||
for (size_t block_id = 0; block_id < block_count; block_id++) {
|
||||
std::string block;
|
||||
for (size_t line = 0; block.size() < block_size; line++) {
|
||||
block += PSTRING() << "\nblock=" << block_id << ", line=" << line;
|
||||
}
|
||||
block.resize(block_size);
|
||||
content += block;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
class TestTd : public Actor {
|
||||
public:
|
||||
struct Options {
|
||||
std::string alice_dir = "alice";
|
||||
std::string bob_dir = "bob";
|
||||
int32 api_id{0};
|
||||
string api_hash;
|
||||
};
|
||||
|
||||
TestTd(Options options) : options_(std::move(options)) {
|
||||
}
|
||||
|
||||
private:
|
||||
Options options_;
|
||||
ActorOwn<TestClient> alice_;
|
||||
GetMe::Result alice_id_;
|
||||
std::string alice_cache_dir_;
|
||||
ActorOwn<TestClient> bob_;
|
||||
|
||||
void start_up() override {
|
||||
alice_ = create_actor<TestClient>("Alice", "Alice");
|
||||
bob_ = create_actor<TestClient>("Bob", "Bob");
|
||||
|
||||
MultiPromiseActorSafe mp("init");
|
||||
mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_init));
|
||||
|
||||
InitTask::Options options;
|
||||
options.api_id = options_.api_id;
|
||||
options.api_hash = options_.api_hash;
|
||||
|
||||
options.name = options_.alice_dir;
|
||||
td::send_closure(alice_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
|
||||
options.name = options_.bob_dir;
|
||||
td::send_closure(bob_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
|
||||
}
|
||||
|
||||
void check_init(Result<Unit> res) {
|
||||
LOG_IF(FATAL, res.is_error()) << res.error();
|
||||
alice_cache_dir_ = options_.alice_dir + TD_DIR_SLASH + "cache";
|
||||
mkdir(alice_cache_dir_).ignore();
|
||||
|
||||
td::send_closure(alice_, &TestClient::add_listener,
|
||||
td::make_unique<GetMe>(promise_send_closure(actor_id(this), &TestTd::with_alice_id)));
|
||||
|
||||
//close();
|
||||
}
|
||||
|
||||
void with_alice_id(Result<GetMe::Result> alice_id) {
|
||||
alice_id_ = alice_id.move_as_ok();
|
||||
LOG(ERROR) << "Alice user_id=" << alice_id_.user_id << ", chat_id=" << alice_id_.chat_id;
|
||||
auto content = gen_readable_file(65536, 20);
|
||||
send_closure(alice_, &TestClient::add_listener,
|
||||
td::make_unique<UploadFile>(alice_cache_dir_, std::move(content), alice_id_.chat_id, promise_send_closure(actor_id(this), &TestTd::with_file)));
|
||||
}
|
||||
void with_file(Result<UploadFile::Result> r_result) {
|
||||
auto result = r_result.move_as_ok();
|
||||
send_closure(alice_, &TestClient::add_listener,
|
||||
td::make_unique<TestDownloadFile>(result.remote_id, std::move(result.content), promise_send_closure(actor_id(this), &TestTd::after_test_download_file)));
|
||||
}
|
||||
void after_test_download_file(Result<Unit>) {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void close() {
|
||||
MultiPromiseActorSafe mp("close");
|
||||
mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_close));
|
||||
td::send_closure(alice_, &TestClient::close, mp.get_promise());
|
||||
td::send_closure(bob_, &TestClient::close, mp.get_promise());
|
||||
}
|
||||
|
||||
void check_close(Result<Unit> res) {
|
||||
Scheduler::instance()->finish();
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
static void fail_signal(int sig) {
|
||||
signal_safe_write_signal_number(sig);
|
||||
while (true) {
|
||||
// spin forever to allow debugger to attach
|
||||
}
|
||||
}
|
||||
|
||||
static void on_fatal_error(const char *error) {
|
||||
std::cerr << "Fatal error: " << error << std::endl;
|
||||
}
|
||||
int main(int argc, char **argv) {
|
||||
ignore_signal(SignalType::HangUp).ensure();
|
||||
ignore_signal(SignalType::Pipe).ensure();
|
||||
set_signal_handler(SignalType::Error, fail_signal).ensure();
|
||||
set_signal_handler(SignalType::Abort, fail_signal).ensure();
|
||||
Log::set_fatal_error_callback(on_fatal_error);
|
||||
init_openssl_threads();
|
||||
|
||||
TestTd::Options test_options;
|
||||
|
||||
test_options.api_id = [](auto x) -> int32 {
|
||||
if (x) {
|
||||
return to_integer<int32>(Slice(x));
|
||||
}
|
||||
return 0;
|
||||
}(std::getenv("TD_API_ID"));
|
||||
test_options.api_hash = [](auto x) -> std::string {
|
||||
if (x) {
|
||||
return x;
|
||||
}
|
||||
return std::string();
|
||||
}(std::getenv("TD_API_HASH"));
|
||||
|
||||
int new_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
|
||||
OptionParser options;
|
||||
options.set_description("TDLib experimental tester");
|
||||
options.add_option('v', "verbosity", "Set verbosity level", [&](Slice level) {
|
||||
int new_verbosity = 1;
|
||||
while (begins_with(level, "v")) {
|
||||
new_verbosity++;
|
||||
level.remove_prefix(1);
|
||||
}
|
||||
if (!level.empty()) {
|
||||
new_verbosity += to_integer<int>(level) - (new_verbosity == 1);
|
||||
}
|
||||
new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
|
||||
});
|
||||
options.add_check([&] {
|
||||
if (test_options.api_id == 0 || test_options.api_hash.empty()) {
|
||||
return Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
|
||||
}
|
||||
return Status::OK();
|
||||
});
|
||||
auto r_non_options = options.run(argc, argv, 0);
|
||||
if (r_non_options.is_error()) {
|
||||
LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
|
||||
LOG(PLAIN) << options;
|
||||
return 1;
|
||||
}
|
||||
SET_VERBOSITY_LEVEL(new_verbosity_level);
|
||||
|
||||
td::ConcurrentScheduler sched;
|
||||
sched.init(4);
|
||||
sched.create_actor_unsafe<TestTd>(0, "TestTd", std::move(test_options)).release();
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
}
|
||||
sched.finish();
|
||||
return 0;
|
||||
}
|
||||
} // namespace td
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return td::main(argc, argv);
|
||||
}
|
Loading…
Reference in New Issue
Block a user