From e96331fdab436e9973e41bb1ecc92b908118cb71 Mon Sep 17 00:00:00 2001 From: Yuval Lifshitz Date: Wed, 28 Oct 2020 18:10:20 +0200 Subject: [PATCH] rgw/lua: use lua exceptions instead of ceph_asserts also use lua assert() in uni tests instead of print() fix bug when accessing user id in an ACL grant Signed-off-by: Yuval Lifshitz --- src/rgw/rgw_acl.h | 14 +++ src/rgw/rgw_lua_request.cc | 86 +++++++----------- src/test/rgw/test_rgw_lua.cc | 168 ++++++++++++++++++----------------- 3 files changed, 131 insertions(+), 137 deletions(-) diff --git a/src/rgw/rgw_acl.h b/src/rgw/rgw_acl.h index ad09eee1645..9e8a0e5dd49 100644 --- a/src/rgw/rgw_acl.h +++ b/src/rgw/rgw_acl.h @@ -117,6 +117,7 @@ protected: ACLGranteeType type; rgw_user id; string email; + mutable rgw_user email_id; ACLPermission permission; string name; ACLGroupTypeEnum group; @@ -142,6 +143,19 @@ public: } } + const rgw_user* get_id() const { + switch(type.get_type()) { + case ACL_TYPE_EMAIL_USER: + email_id.from_str(email); + return &email_id; + case ACL_TYPE_GROUP: + case ACL_TYPE_REFERER: + return nullptr; + default: + return &id; + } + } + ACLGranteeType& get_type() { return type; } const ACLGranteeType& get_type() const { return type; } ACLPermission& get_permission() { return permission; } diff --git a/src/rgw/rgw_lua_request.cc b/src/rgw/rgw_lua_request.cc index 6e3b148df3a..1d28812496c 100644 --- a/src/rgw/rgw_lua_request.cc +++ b/src/rgw/rgw_lua_request.cc @@ -46,8 +46,7 @@ struct ResponseMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto err = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "HTTPStatusCode") == 0) { lua_pushinteger(L, err->http_ret); @@ -66,17 +65,16 @@ struct ResponseMetaTable : public EmptyMetaTable { static int NewIndexClosure(lua_State* L) { auto err = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -2)); - const char* index = lua_tostring(L, -2); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "HTTPStatusCode") == 0) { - err->http_ret = lua_tointeger(L, -1); + err->http_ret = luaL_checkinteger(L, 3); } else if (strcasecmp(index, "RGWCode") == 0) { - err->ret = lua_tointeger(L, -1); + err->ret = luaL_checkinteger(L, 3); } else if (strcasecmp(index, "HTTPStatus") == 0) { - err->err_code.assign(lua_tostring(L, -1)); + err->err_code.assign(luaL_checkstring(L, 3)); } else if (strcasecmp(index, "Message") == 0) { - err->message.assign(lua_tostring(L, -1)); + err->message.assign(luaL_checkstring(L, 3)); } else { throw_unknown_field(index, TableName()); } @@ -91,8 +89,7 @@ struct QuotaMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto info = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "MaxSize") == 0) { lua_pushinteger(L, info->max_size); @@ -116,8 +113,7 @@ struct PlacementRuleMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto rule = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Name") == 0) { pushstring(L, rule->name); @@ -137,8 +133,7 @@ struct UserMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto user = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Tenant") == 0) { pushstring(L, user->tenant); @@ -158,8 +153,7 @@ struct OwnerMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto owner = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "DisplayName") == 0) { pushstring(L, owner->get_display_name()); @@ -181,8 +175,7 @@ struct BucketMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto bucket = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Tenant") == 0) { pushstring(L, bucket->get_tenant()); @@ -224,8 +217,7 @@ struct ObjectMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto obj = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Name") == 0) { pushstring(L, obj->get_name()); @@ -250,10 +242,8 @@ template int StringMapWriteableNewIndex(lua_State* L) { const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - ceph_assert(lua_isstring(L, -2)); - const char* value = lua_tostring(L, -1); - const char* index = lua_tostring(L, -2); + const char* index = luaL_checkstring(L, 2); + const char* value = luaL_checkstring(L, 3); map->insert_or_assign(index, value); return NO_RETURNVAL; } @@ -268,8 +258,7 @@ struct StringMapMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); const auto it = map->find(std::string(index)); if (it == map->end()) { @@ -302,8 +291,7 @@ struct StringMapMetaTable : public EmptyMetaTable { if (lua_isnil(L, -1)) { next_it = map->begin(); } else { - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); const auto it = map->find(std::string(index)); ceph_assert(it != map->end()); next_it = std::next(it); @@ -339,15 +327,14 @@ struct GrantMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto grant = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Type") == 0) { lua_pushinteger(L, grant->get_type().get_type()); } else if (strcasecmp(index, "User") == 0) { - rgw_user id; - if (grant->get_id(id)) { - create_metatable(L, false, &id); + const auto id_ptr = grant->get_id(); + if (id_ptr) { + create_metatable(L, false, const_cast(id_ptr)); } else { lua_pushnil(L); } @@ -371,8 +358,7 @@ struct GrantsMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); const auto it = map->find(std::string(index)); if (it == map->end()) { @@ -401,8 +387,7 @@ struct GrantsMetaTable : public EmptyMetaTable { if (lua_isnil(L, -1)) { next_it = map->begin(); } else { - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); const auto it = map->find(std::string(index)); ceph_assert(it != map->end()); next_it = std::next(it); @@ -453,8 +438,7 @@ struct ACLMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto acl = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Owner") == 0) { create_metatable(L, false, &(acl->get_owner())); @@ -482,7 +466,7 @@ struct StatementsMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto statements = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - const auto index = lua_tointeger(L, -1); + const auto index = luaL_checkinteger(L, 2); if (index >= (int)statements->size() || index < 0) { lua_pushnil(L); @@ -510,8 +494,7 @@ struct StatementsMetaTable : public EmptyMetaTable { if (lua_isnil(L, -1)) { next_it = 0; } else { - ceph_assert(lua_isinteger(L, -1)); - const auto it = lua_tointeger(L, -1); + const auto it = luaL_checkinteger(L, -1); next_it = it+1; } @@ -545,8 +528,7 @@ struct PolicyMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto policy = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Text") == 0) { pushstring(L, policy->text); @@ -575,7 +557,7 @@ struct PoliciesMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto policies = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - const auto index = lua_tointeger(L, -1); + const auto index = luaL_checkinteger(L, 2); if (index >= (int)policies->size() || index < 0) { lua_pushnil(L); @@ -603,7 +585,7 @@ struct PoliciesMetaTable : public EmptyMetaTable { next_it = 0; } else { ceph_assert(lua_isinteger(L, -1)); - const auto it = lua_tointeger(L, -1); + const auto it = luaL_checkinteger(L, -1); next_it = it+1; } @@ -637,8 +619,7 @@ struct HTTPMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto info = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Parameters") == 0) { create_metatable>(L, false, &(info->args.get_params())); @@ -672,8 +653,7 @@ struct CopyFromMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto s = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Tenant") == 0) { pushstring(L, s->src_tenant_name); @@ -695,8 +675,7 @@ struct ZoneGroupMetaTable : public EmptyMetaTable { static int IndexClosure(lua_State* L) { const auto s = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "Name") == 0) { pushstring(L, s->zonegroup_name); @@ -718,8 +697,7 @@ struct RequestMetaTable : public EmptyMetaTable { const auto s = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); const auto op_name = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); - ceph_assert(lua_isstring(L, -1)); - const char* index = lua_tostring(L, -1); + const char* index = luaL_checkstring(L, 2); if (strcasecmp(index, "RGWOp") == 0) { pushstring(L, op_name); diff --git a/src/test/rgw/test_rgw_lua.cc b/src/test/rgw/test_rgw_lua.cc index 6cae8ebf018..c3cffad6ab1 100644 --- a/src/test/rgw/test_rgw_lua.cc +++ b/src/test/rgw/test_rgw_lua.cc @@ -138,9 +138,8 @@ TEST(TestRGWLua, RGWDebugNil) TEST(TestRGWLua, URI) { const std::string script = R"( - msg = "URI is: " .. Request.DecodedURI - RGWDebugLog(msg) - print(msg) + RGWDebugLog(Request.DecodedURI) + assert(Request.DecodedURI == "http://hello.world/") )"; DEFINE_REQ_STATE; @@ -153,10 +152,10 @@ TEST(TestRGWLua, URI) TEST(TestRGWLua, Response) { const std::string script = R"( - print(Request.Response.Message) - print(Request.Response.HTTPStatus) - print(Request.Response.RGWCode) - print(Request.Response.HTTPStatusCode) + assert(Request.Response.Message == "This is a bad request") + assert(Request.Response.HTTPStatus == "Bad Request") + assert(Request.Response.RGWCode == 4000) + assert(Request.Response.HTTPStatusCode == 400) )"; DEFINE_REQ_STATE; @@ -172,9 +171,9 @@ TEST(TestRGWLua, Response) TEST(TestRGWLua, SetResponse) { const std::string script = R"( - print(Request.Response.Message) + assert(Request.Response.Message == "this is a bad request") Request.Response.Message = "this is a good request" - print(Request.Response.Message) + assert(Request.Response.Message == "this is a good request") )"; DEFINE_REQ_STATE; @@ -187,9 +186,8 @@ TEST(TestRGWLua, SetResponse) TEST(TestRGWLua, SetRGWId) { const std::string script = R"( - print(Request.RGWId) + assert(Request.RGWId == "foo") Request.RGWId = "bar" - print(Request.RGWId) )"; DEFINE_REQ_STATE; @@ -227,27 +225,22 @@ TEST(TestRGWLua, InvalidSubField) TEST(TestRGWLua, Bucket) { const std::string script = R"( - if Request.Bucket then - msg = "Bucket Id: " .. Request.Bucket.Id - RGWDebugLog(msg) - print(msg) - print("Bucket Marker: " .. Request.Bucket.Marker) - print("Bucket Name: " .. Request.Bucket.Name) - print("Bucket Tenant: " .. Request.Bucket.Tenant) - print("Bucket Count: " .. Request.Bucket.Count) - print("Bucket Size: " .. Request.Bucket.Size) - print("Bucket ZoneGroupId: " .. Request.Bucket.ZoneGroupId) - print("Bucket Creation Time: " .. Request.Bucket.CreationTime) - print("Bucket MTime: " .. Request.Bucket.MTime) - print("Bucket Quota Max Size: " .. Request.Bucket.Quota.MaxSize) - print("Bucket Quota Max Objects: " .. Request.Bucket.Quota.MaxObjects) - print("Bucket Quota Enabled: " .. tostring(Request.Bucket.Quota.Enabled)) - print("Bucket Quota Rounded: " .. tostring(Request.Bucket.Quota.Rounded)) - print("Bucket User Id: " .. Request.Bucket.User.Id) - print("Bucket User Tenant: " .. Request.Bucket.User.Tenant) - else - print("No bucket") - end + assert(Request.Bucket) + RGWDebugLog("Bucket Id: " .. Request.Bucket.Id) + assert(Request.Bucket.Marker == "mymarker") + assert(Request.Bucket.Name == "myname") + assert(Request.Bucket.Tenant == "mytenant") + assert(Request.Bucket.Count == 0) + assert(Request.Bucket.Size == 0) + assert(Request.Bucket.ZoneGroupId) + assert(Request.Bucket.CreationTime) + assert(Request.Bucket.MTime) + assert(Request.Bucket.Quota.MaxSize == -1) + assert(Request.Bucket.Quota.MaxObjects == -1) + assert(tostring(Request.Bucket.Quota.Enabled)) + assert(tostring(Request.Bucket.Quota.Rounded)) + assert(Request.Bucket.User.Id) + assert(Request.Bucket.User.Tenant) )"; DEFINE_REQ_STATE; @@ -266,12 +259,13 @@ TEST(TestRGWLua, Bucket) TEST(TestRGWLua, GenericAttributes) { const std::string script = R"( - print("hello = " .. (Request.GenericAttributes["hello"] or "nil")) - print("foo = " .. (Request.GenericAttributes["foo"] or "nil")) - print("kaboom = " .. (Request.GenericAttributes["kaboom"] or "nil")) - print("number of attributes is: " .. #Request.GenericAttributes) + assert(Request.GenericAttributes["hello"] == "world") + assert(Request.GenericAttributes["foo"] == "bar") + assert(Request.GenericAttributes["kaboom"] == nil) + assert(#Request.GenericAttributes == 4) for k, v in pairs(Request.GenericAttributes) do - print("key=" .. k .. ", " .. "value=" .. v) + assert(k) + assert(v) end )"; @@ -288,10 +282,14 @@ TEST(TestRGWLua, GenericAttributes) TEST(TestRGWLua, Environment) { const std::string script = R"( - print("number of env entries is: " .. #Request.Environment) - for k, v in pairs(Request.Environment) do - print("key=" .. k .. ", " .. "value=" .. v) - end + assert(Request.Environment[""] == "bar") + assert(Request.Environment["goodbye"] == "cruel world") + assert(Request.Environment["ka"] == "boom") + assert(#Request.Environment == 3, #Request.Environment) + for k, v in pairs(Request.Environment) do + assert(k) + assert(v) + end )"; DEFINE_REQ_STATE; @@ -307,9 +305,11 @@ TEST(TestRGWLua, Environment) TEST(TestRGWLua, Tags) { const std::string script = R"( - print("number of tags is: " .. #Request.Tags) + assert(#Request.Tags == 4) + assert(Request.Tags["foo"] == "bar") for k, v in pairs(Request.Tags) do - print("key=" .. k .. ", " .. "value=" .. v) + assert(k) + assert(v) end )"; @@ -339,21 +339,18 @@ TEST(TestRGWLua, TagsNotWriteable) TEST(TestRGWLua, Metadata) { const std::string script = R"( - print("number of metadata entries is: " .. #Request.HTTP.Metadata) + assert(#Request.HTTP.Metadata == 3) for k, v in pairs(Request.HTTP.Metadata) do - print("key=" .. k .. ", " .. "value=" .. v) + assert(k) + assert(v) end - print("value of 'hello' is:") - print(Request.HTTP.Metadata["hello"]) - print("value of 'kaboom' is:") - print(Request.HTTP.Metadata["kaboom"]) + assert(Request.HTTP.Metadata["hello"] == "world") + assert(Request.HTTP.Metadata["kaboom"] == nil) Request.HTTP.Metadata["hello"] = "goodbye" Request.HTTP.Metadata["kaboom"] = "boom" - print("new number of metadata entries is: " .. #Request.HTTP.Metadata) - print("new value of 'hello' is:") - print(Request.HTTP.Metadata["hello"]) - print("new value of 'kaboom' is:") - print(Request.HTTP.Metadata["kaboom"]) + assert(#Request.HTTP.Metadata == 4) + assert(Request.HTTP.Metadata["hello"] == "goodbye") + assert(Request.HTTP.Metadata["kaboom"] == "boom") )"; DEFINE_REQ_STATE; @@ -369,36 +366,41 @@ TEST(TestRGWLua, Acl) { const std::string script = R"( function print_grant(g) - print("Type: " .. g.Type) - print("GroupType: " .. g.GroupType) - print("Permission: " .. g.Permission) - print("Referer: " .. g.Referer) + print("Grant Type: " .. g.Type) + print("Grant Group Type: " .. g.GroupType) + print("Grant Referer: " .. g.Referer) if (g.User) then - print("User.Tenant: " .. g.User.Tenant) - print("User.Id: " .. g.User.Id) + print("Grant User.Tenant: " .. g.User.Tenant) + print("Grant User.Id: " .. g.User.Id) end end - print(Request.UserAcl.Owner.DisplayName) - print(Request.UserAcl.Owner.User.Id) - print(Request.UserAcl.Owner.User.Tenant) - print("number of grants is: " .. #Request.UserAcl.Grants) + assert(Request.UserAcl.Owner.DisplayName == "jack black", Request.UserAcl.Owner.DisplayName) + assert(Request.UserAcl.Owner.User.Id == "black", Request.UserAcl.Owner.User.Id) + assert(Request.UserAcl.Owner.User.Tenant == "jack", Request.UserAcl.Owner.User.Tenant) + assert(#Request.UserAcl.Grants == 5) + print_grant(Request.UserAcl.Grants[""]) for k, v in pairs(Request.UserAcl.Grants) do - print("grant key=" .. k) - print("grant values=") print_grant(v) + if k == "john$doe" then + assert(v.Permission == 4) + elseif k == "jane$doe" then + assert(v.Permission == 1) + else + assert(false) + end end )"; DEFINE_REQ_STATE; ACLOwner owner; - owner.set_id(rgw_user("john", "doe")); - owner.set_name("john doe"); + owner.set_id(rgw_user("jack", "black")); + owner.set_name("jack black"); s.user_acl.reset(new RGWAccessControlPolicy(cct)); s.user_acl->set_owner(owner); ACLGrant grant1, grant2, grant3, grant4, grant5; grant1.set_canon(rgw_user("jane", "doe"), "her grant", 1); - grant2.set_referer("http://localhost/ref1", 2); + grant2.set_group(ACL_GROUP_ALL_USERS ,2); grant3.set_referer("http://localhost/ref2", 3); grant4.set_canon(rgw_user("john", "doe"), "his grant", 4); grant5.set_group(ACL_GROUP_AUTHENTICATED_USERS, 5); @@ -458,9 +460,11 @@ TEST(TestRGWLua, UseFunction) TEST(TestRGWLua, WithLib) { const std::string script = R"( - print("bucket name split:") - for i in string.gmatch(Request.Bucket.Name, "%a+") do - print("lua print: part: " .. i) + expected_result = {"my", "bucket", "name", "is", "fish"} + i = 1 + for p in string.gmatch(Request.Bucket.Name, "%a+") do + assert(p == expected_result[i]) + i = i + 1 end )"; @@ -477,6 +481,8 @@ TEST(TestRGWLua, WithLib) #include #include +bool unix_socket_client_ended_ok = false; + void unix_socket_client(const std::string& path) { int fd; // create the socket @@ -497,11 +503,11 @@ void unix_socket_client(const std::string& path) { return; } - std::cout << "unix socket connected to: " << path << std::endl; char buff[256]; int rc; while((rc=read(fd, buff, sizeof(buff))) > 0) { std::cout << std::string(buff, rc); + unix_socket_client_ended_ok = true; } } @@ -514,15 +520,10 @@ TEST(TestRGWLua, OpsLog) const std::string script = R"( if Request.Response.HTTPStatusCode == 200 then - print("request is good, just log to lua: " .. Request.Response.Message) + assert(Request.Response.Message == "Life is great") else - print("request is bad, use ops log:") - if Request.Bucket then - rc = Request.Log() - print("ops log return code: " .. rc) - else - print("no bucket, ops log wasn't called") - end + assert(Request.Bucket) + assert(Request.Log() == 0) end )"; @@ -558,7 +559,8 @@ TEST(TestRGWLua, OpsLog) EXPECT_EQ(rc, 0); // give the socket client time to read - std::this_thread::sleep_for(std::chrono::seconds(2)); - unix_socket_thread.detach(); + std::this_thread::sleep_for(std::chrono::seconds(5)); + unix_socket_thread.detach(); // read is stuck there, so we cannot join + EXPECT_TRUE(unix_socket_client_ended_ok); } -- 2.39.5