bthread_fd_unittest.cpp 15.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
gejun's avatar
gejun committed
17

gejun's avatar
gejun committed
18
#include "butil/compat.h"
gejun's avatar
gejun committed
19 20 21 22 23
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/utsname.h>                           // uname
#include <fcntl.h>
#include <gtest/gtest.h>
24
#include <pthread.h>
25
#include "butil/gperftools_profiler.h"
26 27 28 29
#include "butil/time.h"
#include "butil/macros.h"
#include "butil/fd_utility.h"
#include "butil/logging.h"
gejun's avatar
gejun committed
30 31 32 33 34
#include "bthread/task_control.h"
#include "bthread/task_group.h"
#include "bthread/interrupt_pthread.h"
#include "bthread/bthread.h"
#include "bthread/unstable.h"
35 36 37 38
#if defined(OS_MACOSX)
#include <sys/types.h>                           // struct kevent
#include <sys/event.h>                           // kevent(), kqueue()
#endif
gejun's avatar
gejun committed
39 40 41

#ifndef NDEBUG
namespace bthread {
42
extern butil::atomic<int> break_nums;
gejun's avatar
gejun committed
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
extern TaskControl* global_task_control;
int stop_and_join_epoll_threads();
}
#endif

namespace {
TEST(FDTest, read_kernel_version) {
    utsname name;
    uname(&name);
    std::cout << "sysname=" << name.sysname << std::endl
         << "nodename=" << name.nodename << std::endl
         << "release=" << name.release << std::endl
         << "version=" << name.version << std::endl
         << "machine=" << name.machine << std::endl;
}

#define RUN_CLIENT_IN_BTHREAD 1
//#define USE_BLOCKING_EPOLL 1
//#define RUN_EPOLL_IN_BTHREAD 1
//#define CREATE_THREAD_TO_PROCESS 1

volatile bool stop = false;

struct SocketMeta {
    int fd;
    int epfd;
};

struct BAIDU_CACHELINE_ALIGNMENT ClientMeta {
    int fd;
    size_t count;
    size_t times;
};

struct EpollMeta {
    int epfd;
};

81
const size_t NCLIENT = 30;
gejun's avatar
gejun committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
void* process_thread(void* arg) {
    SocketMeta* m = (SocketMeta*)arg;
    size_t count;
    //printf("begin to process fd=%d\n", m->fd);
    ssize_t n = read(m->fd, &count, sizeof(count));
    if (n != sizeof(count)) {
        LOG(FATAL) << "Should not happen in this test";
        return NULL;
    }
    count += NCLIENT;
    //printf("write result=%lu to fd=%d\n", count, m->fd);
    if (write(m->fd, &count, sizeof(count)) != sizeof(count)) {
        LOG(FATAL) << "Should not happen in this test";
        return NULL;
    }
#ifdef CREATE_THREAD_TO_PROCESS
98
# if defined(OS_LINUX)
gejun's avatar
gejun committed
99 100 101 102
    epoll_event evt = { EPOLLIN | EPOLLONESHOT, { m } };
    if (epoll_ctl(m->epfd, EPOLL_CTL_MOD, m->fd, &evt) < 0) {
        epoll_ctl(m->epfd, EPOLL_CTL_ADD, m->fd, &evt);
    }
103 104 105 106 107 108
# elif defined(OS_MACOSX)
    struct kevent kqueue_event;
    EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT,
            0, 0, m);
    kevent(m->epfd, &kqueue_event, 1, NULL, 0, NULL);
# endif
gejun's avatar
gejun committed
109 110 111 112 113 114 115 116
#endif
    return NULL;
}

void* epoll_thread(void* arg) {
    bthread_usleep(1);
    EpollMeta* m = (EpollMeta*)arg;
    const int epfd = m->epfd;
117
#if defined(OS_LINUX)
gejun's avatar
gejun committed
118
    epoll_event e[32];
119 120 121
#elif defined(OS_MACOSX)
    struct kevent e[32];
#endif
gejun's avatar
gejun committed
122 123 124

    while (!stop) {

125 126
#if defined(OS_LINUX)
# ifndef USE_BLOCKING_EPOLL
gejun's avatar
gejun committed
127 128 129 130 131 132 133 134
        const int n = epoll_wait(epfd, e, ARRAY_SIZE(e), 0);
        if (stop) {
            break;
        }
        if (n == 0) {
            bthread_fd_wait(epfd, EPOLLIN);
            continue;
        }
135
# else
gejun's avatar
gejun committed
136 137 138 139 140 141 142
        const int n = epoll_wait(epfd, e, ARRAY_SIZE(e), -1);
        if (stop) {
            break;
        }
        if (n == 0) {
            continue;
        }
143 144 145 146 147 148 149 150 151
# endif
#elif defined(OS_MACOSX)
        const int n = kevent(epfd, NULL, 0, e, ARRAY_SIZE(e), NULL);
        if (stop) {
            break;
        }
        if (n == 0) {
            continue;
        }
gejun's avatar
gejun committed
152 153 154 155 156
#endif
        if (n < 0) {
            if (EINTR == errno) {
                continue;
            }
157
#if defined(OS_LINUX)
gejun's avatar
gejun committed
158
            PLOG(FATAL) << "Fail to epoll_wait";
159 160 161
#elif defined(OS_MACOSX)
            PLOG(FATAL) << "Fail to kevent";
#endif
gejun's avatar
gejun committed
162 163 164 165 166 167 168
            break;
        }

#ifdef CREATE_THREAD_TO_PROCESS
        bthread_fvec vec[n];
        for (int i = 0; i < n; ++i) {
            vec[i].fn = process_thread;
169
# if defined(OS_LINUX)
gejun's avatar
gejun committed
170
            vec[i].arg = e[i].data.ptr;
171 172 173
# elif defined(OS_MACOSX)
            vec[i].arg = e[i].udata;
# endif
gejun's avatar
gejun committed
174 175 176 177 178
        }
        bthread_t tid[n];
        bthread_startv(tid, vec, n, &BTHREAD_ATTR_SMALL);
#else
        for (int i = 0; i < n; ++i) {
179
# if defined(OS_LINUX)
gejun's avatar
gejun committed
180
            process_thread(e[i].data.ptr);
181 182 183
# elif defined(OS_MACOSX)
            process_thread(e[i].udata);
# endif 
gejun's avatar
gejun committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
        }
#endif        
    }
    return NULL;
}

void* client_thread(void* arg) {
    ClientMeta* m = (ClientMeta*)arg;
    for (size_t i = 0; i < m->times; ++i) {
        if (write(m->fd, &m->count, sizeof(m->count)) != sizeof(m->count)) {
            LOG(FATAL) << "Should not happen in this test";
            return NULL;
        }
#ifdef RUN_CLIENT_IN_BTHREAD
        ssize_t rc;
        do {
200
# if defined(OS_LINUX)
gejun's avatar
gejun committed
201
            const int wait_rc = bthread_fd_wait(m->fd, EPOLLIN);
202 203 204
# elif defined(OS_MACOSX)
            const int wait_rc = bthread_fd_wait(m->fd, EVFILT_READ);
# endif
205
            EXPECT_EQ(0, wait_rc) << berror();
gejun's avatar
gejun committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            rc = read(m->fd, &m->count, sizeof(m->count));
        } while (rc < 0 && errno == EAGAIN);
#else
        ssize_t rc = read(m->fd, &m->count, sizeof(m->count));
#endif
        if (rc != sizeof(m->count)) {
            PLOG(FATAL) << "Should not happen in this test, rc=" << rc;
            return NULL;
        }
    }
    return NULL;
}

inline uint32_t fmix32 ( uint32_t h ) {
    h ^= h >> 16;
    h *= 0x85ebca6b;
    h ^= h >> 13;
    h *= 0xc2b2ae35;
    h ^= h >> 16;
    return h;
}

// Disable temporarily due to epoll's bug. The bug is fixed by
// a kernel patch that lots of machines currently don't have
TEST(FDTest, ping_pong) {
#ifndef NDEBUG
232
    bthread::break_nums = 0;
gejun's avatar
gejun committed
233 234 235 236 237 238
#endif

    const size_t REP = 30000;
    const size_t NEPOLL = 2;

    int epfd[NEPOLL];
gejun's avatar
gejun committed
239
#ifdef RUN_EPOLL_IN_BTHREAD
gejun's avatar
gejun committed
240
    bthread_t eth[NEPOLL];
gejun's avatar
gejun committed
241 242 243
#else
    pthread_t eth[NEPOLL];
#endif
gejun's avatar
gejun committed
244
    int fds[2 * NCLIENT];
245
#ifdef RUN_CLIENT_IN_BTHREAD
gejun's avatar
gejun committed
246
    bthread_t cth[NCLIENT];
247 248 249
#else
    pthread_t cth[NCLIENT];
#endif
gejun's avatar
gejun committed
250 251 252
    ClientMeta* cm[NCLIENT];

    for (size_t i = 0; i < NEPOLL; ++i) {
253
#if defined(OS_LINUX)
gejun's avatar
gejun committed
254
        epfd[i] = epoll_create(1024);
255 256 257
#elif defined(OS_MACOSX)
        epfd[i] = kqueue();
#endif
gejun's avatar
gejun committed
258 259 260 261 262 263 264 265 266 267 268 269
        ASSERT_GT(epfd[i], 0);
    }
    
    for (size_t i = 0; i < NCLIENT; ++i) {
        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds + 2 * i));
        //printf("Created fd=%d,%d i=%lu\n", fds[2*i], fds[2*i+1], i);
        SocketMeta* m = new SocketMeta;
        m->fd = fds[i * 2];
        m->epfd = epfd[fmix32(i) % NEPOLL];
        ASSERT_EQ(0, fcntl(m->fd, F_SETFL, fcntl(m->fd, F_GETFL, 0) | O_NONBLOCK));

#ifdef CREATE_THREAD_TO_PROCESS
270
# if defined(OS_LINUX)
gejun's avatar
gejun committed
271
        epoll_event evt = { EPOLLIN | EPOLLONESHOT, { m } };
272 273 274 275 276
# elif defined(OS_MACOSX)
        struct kevent kqueue_event;
        EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT,
                0, 0, m);
# endif
gejun's avatar
gejun committed
277
#else
278
# if defined(OS_LINUX)
gejun's avatar
gejun committed
279
        epoll_event evt = { EPOLLIN, { m } };
280 281 282 283
# elif defined(OS_MACOSX)
        struct kevent kqueue_event;
        EV_SET(&kqueue_event, m->fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, m);
# endif
gejun's avatar
gejun committed
284 285
#endif

286 287 288 289 290
#if defined(OS_LINUX)
        ASSERT_EQ(0, epoll_ctl(m->epfd, EPOLL_CTL_ADD, m->fd, &evt));
#elif defined(OS_MACOSX)
        ASSERT_EQ(0, kevent(m->epfd, &kqueue_event, 1, NULL, 0, NULL));
#endif
gejun's avatar
gejun committed
291 292 293 294 295
        cm[i] = new ClientMeta;
        cm[i]->fd = fds[i * 2 + 1];
        cm[i]->count = i;
        cm[i]->times = REP;
#ifdef RUN_CLIENT_IN_BTHREAD
296
        butil::make_non_blocking(cm[i]->fd);
gejun's avatar
gejun committed
297 298 299 300 301 302 303
        ASSERT_EQ(0, bthread_start_urgent(&cth[i], NULL, client_thread, cm[i]));
#else
        ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i]));
#endif
    }

    ProfilerStart("ping_pong.prof");
304
    butil::Timer tm;
gejun's avatar
gejun committed
305 306 307 308 309 310
    tm.start();

    for (size_t i = 0; i < NEPOLL; ++i) {
        EpollMeta *em = new EpollMeta;
        em->epfd = epfd[i];
#ifdef RUN_EPOLL_IN_BTHREAD
gejun's avatar
gejun committed
311
        ASSERT_EQ(0, bthread_start_urgent(&eth[i], epoll_thread, em, NULL);
gejun's avatar
gejun committed
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
#else
        ASSERT_EQ(0, pthread_create(&eth[i], NULL, epoll_thread, em));
#endif
    }

    for (size_t i = 0; i < NCLIENT; ++i) {
#ifdef RUN_CLIENT_IN_BTHREAD
        bthread_join(cth[i], NULL);
#else
        pthread_join(cth[i], NULL);
#endif
        ASSERT_EQ(i + REP * NCLIENT, cm[i]->count);
    }
    tm.stop();
    ProfilerStop();
    LOG(INFO) << "tid=" << REP*NCLIENT*1000000L/tm.u_elapsed();
    stop = true;
    for (size_t i = 0; i < NEPOLL; ++i) {
330
#if defined(OS_LINUX)
gejun's avatar
gejun committed
331 332
        epoll_event evt = { EPOLLOUT,  { NULL } };
        ASSERT_EQ(0, epoll_ctl(epfd[i], EPOLL_CTL_ADD, 0, &evt));
333 334 335 336 337
#elif defined(OS_MACOSX)
        struct kevent kqueue_event;
        EV_SET(&kqueue_event, 0, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL);
        ASSERT_EQ(0, kevent(epfd[i], &kqueue_event, 1, NULL, 0, NULL));
#endif
gejun's avatar
gejun committed
338 339 340 341 342 343 344 345 346 347
#ifdef RUN_EPOLL_IN_BTHREAD
        bthread_join(eth[i], NULL);
#else
        pthread_join(eth[i], NULL);
#endif
    }
    //bthread::stop_and_join_epoll_threads();
    bthread_usleep(100000);

#ifndef NDEBUG
348
    std::cout << "break_nums=" << bthread::break_nums << std::endl;
gejun's avatar
gejun committed
349 350 351 352
#endif
}

TEST(FDTest, mod_closed_fd) {
353
#if defined(OS_LINUX)
gejun's avatar
gejun committed
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    // Conclusion:
    //   If fd is never added into epoll, MOD returns ENOENT
    //   If fd is inside epoll and valid, MOD returns 0
    //   If fd is closed and not-reused, MOD returns EBADF
    //   If fd is closed and reused, MOD returns ENOENT again
    
    const int epfd = epoll_create(1024);
    int new_fd[2];
    int fd[2];
    ASSERT_EQ(0, pipe(fd));
    epoll_event e = { EPOLLIN, { NULL } };
    errno = 0;
    ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e));
    ASSERT_EQ(ENOENT, errno);
    ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &e));
    // mod after add
    ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e));
    // mod after mod
    ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e));
    ASSERT_EQ(0, close(fd[0]));
    ASSERT_EQ(0, close(fd[1]));

    errno = 0;
    ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e));
    ASSERT_EQ(EBADF, errno) << berror();

    ASSERT_EQ(0, pipe(new_fd));
    ASSERT_EQ(fd[0], new_fd[0]);
    ASSERT_EQ(fd[1], new_fd[1]);
    
    errno = 0;
    ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_MOD, fd[0], &e));
    ASSERT_EQ(ENOENT, errno) << berror();
    
    ASSERT_EQ(0, close(epfd));
389
#endif
gejun's avatar
gejun committed
390 391 392
}

TEST(FDTest, add_existing_fd) {
393
#if defined(OS_LINUX)
gejun's avatar
gejun committed
394 395 396 397 398 399 400
    const int epfd = epoll_create(1024);
    epoll_event e = { EPOLLIN, { NULL } };
    ASSERT_EQ(0, epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &e));
    errno = 0;
    ASSERT_EQ(-1, epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &e));
    ASSERT_EQ(EEXIST, errno);
    ASSERT_EQ(0, close(epfd));
401
#endif
gejun's avatar
gejun committed
402 403 404
}

void* epoll_waiter(void* arg) {
405
#if defined(OS_LINUX)
gejun's avatar
gejun committed
406 407 408 409
    epoll_event e;
    if (1 == epoll_wait((int)(intptr_t)arg, &e, 1, -1)) {
        std::cout << e.events << std::endl;
    }
410 411 412 413 414 415
#elif defined(OS_MACOSX)
    struct kevent e;
    if (1 == kevent((int)(intptr_t)arg, NULL, 0, &e, 1, NULL)) {
        std::cout << e.flags << std::endl;
    }
#endif
gejun's avatar
gejun committed
416 417 418 419 420
    std::cout << pthread_self() << " quits" << std::endl;
    return NULL;
}

TEST(FDTest, interrupt_pthread) {
421
#if defined(OS_LINUX)
gejun's avatar
gejun committed
422
    const int epfd = epoll_create(1024);
423 424 425
#elif defined(OS_MACOSX)
    const int epfd = kqueue();
#endif
gejun's avatar
gejun committed
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
    pthread_t th, th2;
    ASSERT_EQ(0, pthread_create(&th, NULL, epoll_waiter, (void*)(intptr_t)epfd));
    ASSERT_EQ(0, pthread_create(&th2, NULL, epoll_waiter, (void*)(intptr_t)epfd));
    bthread_usleep(100000L);
    std::cout << "wake up " << th << std::endl;
    bthread::interrupt_pthread(th);
    bthread_usleep(100000L);
    std::cout << "wake up " << th2 << std::endl;
    bthread::interrupt_pthread(th2);
    pthread_join(th, NULL);
    pthread_join(th2, NULL);
}

void* close_the_fd(void* arg) {
    bthread_usleep(10000/*10ms*/);
    EXPECT_EQ(0, bthread_close(*(int*)arg));
    return NULL;
}

TEST(FDTest, invalid_epoll_events) {
    errno = 0;
447
#if defined(OS_LINUX)
gejun's avatar
gejun committed
448
    ASSERT_EQ(-1, bthread_fd_wait(-1, EPOLLIN));
449 450 451
#elif defined(OS_MACOSX)
    ASSERT_EQ(-1, bthread_fd_wait(-1, EVFILT_READ));
#endif
gejun's avatar
gejun committed
452 453
    ASSERT_EQ(EINVAL, errno);
    errno = 0;
454
#if defined(OS_LINUX)
gejun's avatar
gejun committed
455
    ASSERT_EQ(-1, bthread_fd_timedwait(-1, EPOLLIN, NULL));
456 457 458
#elif defined(OS_MACOSX)
    ASSERT_EQ(-1, bthread_fd_timedwait(-1, EVFILT_READ, NULL));
#endif
gejun's avatar
gejun committed
459 460 461 462
    ASSERT_EQ(EINVAL, errno);

    int fds[2];
    ASSERT_EQ(0, pipe(fds));
463
#if defined(OS_LINUX)
gejun's avatar
gejun committed
464 465
    ASSERT_EQ(-1, bthread_fd_wait(fds[0], EPOLLET));
    ASSERT_EQ(EINVAL, errno);
466
#endif
gejun's avatar
gejun committed
467 468
    bthread_t th;
    ASSERT_EQ(0, bthread_start_urgent(&th, NULL, close_the_fd, &fds[1]));
469
    butil::Timer tm;
gejun's avatar
gejun committed
470
    tm.start();
471
#if defined(OS_LINUX)
gejun's avatar
gejun committed
472
    ASSERT_EQ(0, bthread_fd_wait(fds[0], EPOLLIN | EPOLLET));
473 474 475
#elif defined(OS_MACOSX)
    ASSERT_EQ(0, bthread_fd_wait(fds[0], EVFILT_READ));
#endif
gejun's avatar
gejun committed
476 477 478 479 480 481 482
    tm.stop();
    ASSERT_LT(tm.m_elapsed(), 20);
    ASSERT_EQ(0, bthread_join(th, NULL));
    ASSERT_EQ(0, bthread_close(fds[0]));
}

void* wait_for_the_fd(void* arg) {
483
    timespec ts = butil::milliseconds_from_now(50);
484
#if defined(OS_LINUX)
gejun's avatar
gejun committed
485
    bthread_fd_timedwait(*(int*)arg, EPOLLIN, &ts);
486 487 488
#elif defined(OS_MACOSX)
    bthread_fd_timedwait(*(int*)arg, EVFILT_READ, &ts);
#endif
gejun's avatar
gejun committed
489 490 491 492 493 494 495 496 497 498
    return NULL;
}

TEST(FDTest, timeout) {
    int fds[2];
    ASSERT_EQ(0, pipe(fds));
    pthread_t th;
    ASSERT_EQ(0, pthread_create(&th, NULL, wait_for_the_fd, &fds[0]));
    bthread_t bth;
    ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, wait_for_the_fd, &fds[0]));
499
    butil::Timer tm;
gejun's avatar
gejun committed
500 501 502 503
    tm.start();
    ASSERT_EQ(0, pthread_join(th, NULL));
    ASSERT_EQ(0, bthread_join(bth, NULL));
    tm.stop();
504
    ASSERT_LT(tm.m_elapsed(), 80);
gejun's avatar
gejun committed
505 506 507 508 509 510 511 512 513
    ASSERT_EQ(0, bthread_close(fds[0]));
    ASSERT_EQ(0, bthread_close(fds[1]));
}

TEST(FDTest, close_should_wakeup_waiter) {
    int fds[2];
    ASSERT_EQ(0, pipe(fds));
    bthread_t bth;
    ASSERT_EQ(0, bthread_start_urgent(&bth, NULL, wait_for_the_fd, &fds[0]));
514
    butil::Timer tm;
gejun's avatar
gejun committed
515 516 517 518 519 520 521
    tm.start();
    ASSERT_EQ(0, bthread_close(fds[0]));
    ASSERT_EQ(0, bthread_join(bth, NULL));
    tm.stop();
    ASSERT_LT(tm.m_elapsed(), 5);

    // Launch again, should quit soon due to EBADF
522
#if defined(OS_LINUX)
gejun's avatar
gejun committed
523
    ASSERT_EQ(-1, bthread_fd_timedwait(fds[0], EPOLLIN, NULL));
524 525 526
#elif defined(OS_MACOSX)
    ASSERT_EQ(-1, bthread_fd_timedwait(fds[0], EVFILT_READ, NULL));
#endif
gejun's avatar
gejun committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
    ASSERT_EQ(EBADF, errno);

    ASSERT_EQ(0, bthread_close(fds[1]));
}

TEST(FDTest, close_definitely_invalid) {
    int ec = 0;
    ASSERT_EQ(-1, close(-1));
    ec = errno;
    ASSERT_EQ(-1, bthread_close(-1));
    ASSERT_EQ(ec, errno);
}

TEST(FDTest, bthread_close_fd_which_did_not_call_bthread_functions) {
    int fds[2];
    ASSERT_EQ(0, pipe(fds));
    ASSERT_EQ(0, bthread_close(fds[0]));
    ASSERT_EQ(0, bthread_close(fds[1]));
}

TEST(FDTest, double_close) {
    int fds[2];
    ASSERT_EQ(0, pipe(fds));
    ASSERT_EQ(0, close(fds[0]));
    int ec = 0;
    ASSERT_EQ(-1, close(fds[0]));
    ec = errno;
    ASSERT_EQ(0, bthread_close(fds[1]));
    ASSERT_EQ(-1, bthread_close(fds[1]));
    ASSERT_EQ(ec, errno);
}
} // namespace