Edit

kc3-lang/smtp/test/seams.c

Branch :

  • Show log

    Commit

  • Author : humphreyj
    Date : 2018-06-05 14:55:19
    Hash : 1490e2d2
    Message : Add test seams on some of the functions used by the SMTP library. This will allow the main testing framework to inject return values at certain points in order to test different error conditions.

  • test/seams.c
  • /**
     * @file
     * @brief Test seams for the smtp-client library.
     * @author James Humphrey (mail@somnisoft.com)
     * @version 0.99
     *
     * Used by the smtp-client testing framework to inject specific return values
     * by some standard library functions. This makes it possible to test less
     * common errors like out of memory conditions and input/output errors.
     *
     * This software has been placed into the public domain using CC0.
     */
    
    /**
     * Need to define this on some POSIX systems in order to get access to the
     * getaddrinfo and localtime_r functions.
     */
    #define _POSIX_C_SOURCE 200112L
    
    #include <assert.h>
    #include <errno.h>
    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <unistd.h>
    
    #include "test.h"
    
    /**
     * See @ref g_smtp_test_err_bio_new_socket_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_bio_new_socket_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_bio_should_retry_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_bio_should_retry_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_calloc_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_calloc_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_close_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_close_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_connect_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_connect_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_err_peek_error_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_err_peek_error_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_fclose_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_fclose_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ferror_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ferror_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_gmtime_r_ctr @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_gmtime_r_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_hmac_ctr @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_hmac_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_localtime_r_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_localtime_r_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_malloc_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_malloc_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_mktime_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_mktime_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_realloc_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_realloc_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_recv_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_recv_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_recv_bytes and @ref test_seams_countdown_global.
     */
    char g_smtp_test_err_recv_bytes[90] = {0};
    
    /**
     * See @ref g_smtp_test_err_select_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_select_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_send_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_send_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_socket_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_socket_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_connect_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_connect_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_ctx_new_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_ctx_new_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_do_handshake_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_do_handshake_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_get_peer_certificate_ctr and
     * @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_get_peer_certificate_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_new_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_new_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_read_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_read_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_ssl_write_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_ssl_write_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_sprintf_ctr and @ref test_seams_countdown_global.
     */
    int g_smtp_test_err_sprintf_ctr = -1;
    
    /**
     * See @ref g_smtp_test_err_sprintf_rc.
     */
    int g_smtp_test_err_sprintf_rc = 0;
    
    /**
     * See @ref g_smtp_test_time_custom_ret.
     */
    int g_smtp_test_time_custom_ret = 0;
    
    /**
     * See @ref g_smtp_test_time_ret_value.
     */
    time_t g_smtp_test_time_ret_value = 0;
    
    /**
     * Decrement an error counter until it reaches -1.
     *
     * Once a counter reaches -1, it will return a successful response (1). This
     * typically gets used to denote when to cause a function to fail. For example,
     * the unit test or functional test might need to cause the realloc() function
     * to fail after calling it the third time.
     *
     * @param[in,out] test_err_ctr Integer counter to decrement.
     * @retval 0 The counter has been decremented, but did not reach -1 yet.
     * @retval 1 The counter has reached -1.
     */
    static int
    smtp_test_seam_dec_err_ctr(int *const test_err_ctr){
      if(*test_err_ctr >= 0){
        *test_err_ctr -= 1;
        if(*test_err_ctr < 0){
          return 1;
        }
      }
      return 0;
    }
    
    /**
     * Allows the test harness to control when BIO_new_socket() fails.
     *
     * @param[in] sock       Existing socket to attach the BIO to.
     * @param[in] close_flag Close flag for new BIO.
     * @retval BIO* New BIO created on existing socket.
     * @retval NULL Failed to create the new BIO.
     */
    BIO *
    smtp_test_seam_bio_new_socket(int sock,
                                  int close_flag){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_bio_new_socket_ctr)){
        return NULL;
      }
      return BIO_new_socket(sock, close_flag);
    }
    
    /**
     * Allows the test harness to control when BIO_should_retry() fails.
     *
     * @param[in] bio Existing BIO connection.
     * @retval 0 The error condition does not allow a retry.
     * @retval 1 The error condition allows a retry.
     */
    int
    smtp_test_seam_bio_should_retry(BIO *bio){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_bio_should_retry_ctr)){
        return 0;
      }
      return BIO_should_retry(bio);
    }
    
    /**
     * Allows the test harness to control when calloc() fails.
     *
     * @param[in] nelem  Number of elements to allocate.
     * @param[in] elsize Size of each element to allocate.
     * @retval void* Pointer to new allocated memory.
     * @retval NULL  Memory allocation failed.
     */
    void *
    smtp_test_seam_calloc(size_t nelem,
                          size_t elsize){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_calloc_ctr)){
        errno = ENOMEM;
        return NULL;
      }
      return calloc(nelem, elsize);
    }
    
    /**
     * Allows the test harness to control when close() fails.
     *
     * @param[in] fildes Socket file descriptor to close.
     * @retval  0 Successfully closed file descriptor.
     * @retval -1 Failed to close file descriptor.
     */
    int
    smtp_test_seam_close(int fildes){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_close_ctr)){
        errno = EBADF;
        return -1;
      }
      return close(fildes);
    }
    
    /**
     * Allows the test harness to control when connect() fails.
     *
     * @param[in] socket      Socket connection.
     * @param[in] address     Network address of peer.
     * @param[in] address_len Number of bytes in @p address.
     * @retval 0  Successfully connected to the peer.
     * @retval -1 Failed to connect to the peer.
     */
    int
    smtp_test_seam_connect(int socket,
                           const struct sockaddr *address,
                           socklen_t address_len){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_connect_ctr)){
        errno = ECONNREFUSED;
        return -1;
      }
      return connect(socket, address, address_len);
    }
    
    /**
     * Allows the test harness to control when ERR_peek_error() returns a failure
     * code.
     *
     * @retval  0 No error code on the error queue.
     * @retval !0 An error code exists on the error queue.
     */
    unsigned long
    smtp_test_seam_err_peek_error(void){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_err_peek_error_ctr)){
        return -1;
      }
      return ERR_peek_error();
    }
    
    /**
     * Allows the test harness to control when fclose() fails.
     *
     * @param[in] stream File stream to close.
     * @retval 0   Successfully closed the file stream.
     * @retval EOF An error occurred while closing the file stream.
     */
    int smtp_test_seam_fclose(FILE *stream){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_fclose_ctr)){
        errno = EBADF;
        return EOF;
      }
      return fclose(stream);
    }
    
    /**
     * Allows the test harness to control the file stream error indicator return
     * value in ferror().
     *
     * @param[in] stream Check for errors on this file stream.
     * @retval 0 No errors detected on the file stream.
     * @retval 1 An error occurred during a file stream operation.
     */
    int
    smtp_test_seam_ferror(FILE *stream){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ferror_ctr)){
        return 1;
      }
      return ferror(stream);
    }
    
    /**
     * Allows the test harness to control when gmtime_r() fails.
     *
     * @param[in]  timep  Time value to convert to a struct tm.
     * @param[out] result Converts the @p timep value into a UTC tm structure
     *                    value and stores the results in this pointer.
     * @retval tm*  time_t value converted to a tm structure value.
     * @retval NULL An error occurred while converting the time.
     */
    struct tm *
    smtp_test_seam_gmtime_r(const time_t *timep,
                            struct tm *result){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_gmtime_r_ctr)){
        return NULL;
      }
      return gmtime_r(timep, result);
    }
    
    /**
     * Allows the test harness to control when HMAC() fails.
     *
     * @param[in]  evp_md  Hash function.
     * @param[in]  key     Hash key.
     * @param[in]  key_len Number of bytes in @p key.
     * @param[in]  d       Message data.
     * @param[in]  n       Number of bytes in @p d.
     * @param[out] md      The computed message authentication code.
     * @param[in]  md_len  Number of bytes in @p md.
     * @retval uchar* Pointer to @p md.
     * @retval NULL   An error occurred.
     */
    unsigned char *
    smtp_test_seam_hmac(const EVP_MD *evp_md,
                        const void *key,
                        int key_len,
                        const unsigned char *d,
                        int n,
                        unsigned char *md,
                        unsigned int *md_len){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_hmac_ctr)){
        return NULL;
      }
      return HMAC(evp_md, key, key_len, d, n, md, md_len);
    }
    
    /**
     * Allows the test harness to control when localtime_r() fails.
     *
     * @param[in]  timep  Time value to convert to a struct tm.
     * @param[out] result Converts the @p timep value into a local time tm
     *                    structure value and stores the results in this pointer.
     * @retval tm*  time_t value converted to a tm structure value.
     * @retval NULL An error occurred while converting the time.
     */
    struct tm *
    smtp_test_seam_localtime_r(const time_t *timep,
                               struct tm *result){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_localtime_r_ctr)){
        return NULL;
      }
      return localtime_r(timep, result);
    }
    
    /**
     * Allows the test harness to control when malloc() fails.
     *
     * @param[in] size Number of bytes to allocate.
     * @retval void* Pointer to new allocated memory.
     * @retval NULL  Memory allocation failed.
     */
    void *
    smtp_test_seam_malloc(size_t size){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_malloc_ctr)){
        errno = ENOMEM;
        return NULL;
      }
      return malloc(size);
    }
    
    /**
     * Allows the test harness to control when mktime() fails.
     *
     * @param[in] timeptr tm data structure to convert to time_t.
     * @retval >=0 Time since the epoch.
     * @retval -1  Failed to convert the time.
     */
    time_t
    smtp_test_seam_mktime(struct tm *timeptr){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_mktime_ctr)){
        return -1;
      }
      return mktime(timeptr);
    }
    
    /**
     * Allows the test harness to control when realloc() fails.
     *
     * @param[in] ptr  Previously allocated memory or NULL memory has not been
     *                 allocated yet.
     * @param[in] size Number of bytes to reallocate.
     * @retval void* Pointer to new allocated memory.
     * @retval NULL  Memory allocation failed.
     */
    void *
    smtp_test_seam_realloc(void *ptr,
                           size_t size){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_realloc_ctr)){
        errno = ENOMEM;
        return NULL;
      }
      return realloc(ptr, size);
    }
    
    /**
     * Allows the test harness to control when recv() fails.
     *
     * @param[in] socket TCP network socket.
     * @param[in] buffer Store received data in this buffer.
     * @param[in] length Number of bytes in @p buffer.
     * @param[in] flags  Set this to 0.
     * @retval >=0 Number of bytes received.
     * @retval  -1 Failed to receive bytes over the network.
     */
    ssize_t
    smtp_test_seam_recv(int socket,
                        void *buffer,
                        size_t length,
                        int flags){
      size_t bytes_inject_len;
    
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_recv_ctr)){
        if(*g_smtp_test_err_recv_bytes){
          bytes_inject_len = strlen(g_smtp_test_err_recv_bytes);
          assert(bytes_inject_len < length);
          memcpy(buffer, g_smtp_test_err_recv_bytes, bytes_inject_len);
          return bytes_inject_len;
        }
        errno = EBADF;
        return -1;
      }
      return recv(socket, buffer, length, flags);
    }
    
    /**
     * Allows the test harness to control when select() fails.
     *
     * @param[in] nfds     Check for file descriptors in range 0 to (@p nfds - 1)
     *                     which have any of the read/write/error conditions.
     * @param[in] readfds  Checks for file descriptors in fd_set that have bytes
     *                     ready for reading.
     * @param[in] writefds Checks for file descriptors in fd_set that have bytes
     *                     ready for writing.
     * @param[in] errorfds Checks for file descriptors in fd_set that have errors
     *                     pending.
     * @param[in] timeout  Wait for the read/write/error conditions in blocking
     *                     mode until this timeout or an interrupt occurs.
     * @retval >=0 Number of bits set in the bitmask.
     * @retval  -1 An error occurred.
     */
    int
    smtp_test_seam_select(int nfds,
                          fd_set *readfds,
                          fd_set *writefds,
                          fd_set *errorfds,
                          struct timeval *timeout){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_select_ctr)){
        errno = EINTR;
        return -1;
      }
      return select(nfds, readfds, writefds, errorfds, timeout);
    }
    
    /**
     * Allows the test harness to control when send() fails.
     *
     * @param[in] socket TCP network socket.
     * @param[in] buffer Data to send over the network.
     * @param[in] length Number of bytes in @p buffer.
     * @param[in] flags  Set this to 0.
     * @retval >=0 Number of bytes sent.
     * @retval  -1 Failed to send bytes over the network.
     */
    ssize_t
    smtp_test_seam_send(int socket,
                        const void *buffer,
                        size_t length,
                        int flags){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_send_ctr)){
        errno = EBADF;
        return -1;
      }
      return send(socket, buffer, length, flags);
    }
    
    /**
     * Allows the test harness to control when socket() fails.
     *
     * @param[in] domain   Socket domain.
     * @param[in] type     Socket type.
     * @param[in] protocol Socket protocol.
     * @retval !(-1) The file descriptor for the new socket.
     * @retval   -1  Failed to create the socket.
     */
    int
    smtp_test_seam_socket(int domain,
                          int type,
                          int protocol){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_socket_ctr)){
        errno = EINVAL;
        return -1;
      }
      return socket(domain, type, protocol);
    }
    
    /**
     * Allows the test harness to control when SSL_connect() fails.
     *
     * @param[in] ssl OpenSSL handle.
     * @retval  1 TLS connection handshake successful.
     * @retval <1 TLS connection handshake failed.
     */
    int
    smtp_test_seam_ssl_connect(SSL *ssl){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_connect_ctr)){
        return 0;
      }
      return SSL_connect(ssl);
    }
    
    /**
     * Allows the test harness to control when SSL_CTX_new() fails.
     *
     * @param[in] method TLS connection method.
     * @retval SSL_CTX* Pointer to new TLS context.
     * @retval NULL     Failed to create new TLS context.
     */
    SSL_CTX *
    smtp_test_seam_ssl_ctx_new(const SSL_METHOD *method){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_ctx_new_ctr)){
        return NULL;
      }
      return SSL_CTX_new(method);
    }
    
    /**
     * Allows the test harness to control when SSL_do_handshake() fails.
     *
     * @param[in] ssl OpenSSL handle.
     * @retval  1 TLS handshake successful.
     * @retval <1 TLS handshake failed.
     */
    int
    smtp_test_seam_ssl_do_handshake(SSL *ssl){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_do_handshake_ctr)){
        return 0;
      }
      return SSL_do_handshake(ssl);
    }
    
    /**
     * Allows the test harness to control when SSL_get_peer_certificate() fails.
     *
     * @param[in] ssl OpenSSL handle.
     * @retval X509* Peer certficate which must get freed by using X509_free().
     * @retval NULL Failed to get the peer certificate.
     */
    X509 *
    smtp_test_seam_ssl_get_peer_certificate(const SSL *ssl){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_get_peer_certificate_ctr)){
        return NULL;
      }
      return SSL_get_peer_certificate(ssl);
    }
    
    /**
     * Allows the test harness to control when SSL_new() fails.
     *
     * @param[in] ctx OpenSSL TLS context.
     * @retval SSL* Pointer to a new TLS context.
     * @retval NULL Failed to create new TLS context.
     */
    SSL *
    smtp_test_seam_ssl_new(SSL_CTX *ctx){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_new_ctr)){
        return NULL;
      }
      return SSL_new(ctx);
    }
    
    /**
     * Allows the test harness to control when SSL_read() fails.
     *
     * @param[in] ssl OpenSSL TLS object.
     * @param[in] buf Store received data in this buffer.
     * @param[in] num Number of bytes in @p buf.
     * @retval  >0 Number of bytes successfully read from the TLS connection.
     * @retval <=0 Failed to read bytes on the TLS connection.
     */
    int
    smtp_test_seam_ssl_read(SSL *ssl,
                            void *buf,
                            int num){
      size_t bytes_inject_len;
    
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_read_ctr)){
        if(*g_smtp_test_err_recv_bytes){
          bytes_inject_len = strlen(g_smtp_test_err_recv_bytes);
          assert(bytes_inject_len < (size_t)num);
          memcpy(buf, g_smtp_test_err_recv_bytes, bytes_inject_len);
          return bytes_inject_len;
        }
        return -1;
      }
      return SSL_read(ssl, buf, num);
    }
    
    /**
     * Allows the test harness to control when SSL_write() fails.
     *
     * @param[in] ssl OpenSSL TLS object.
     * @param[in] buf Data to write to the TLS connection.
     * @param[in] num Number of bytes in @p buf.
     * @retval  >0 Number of bytes successfully written to the TLS connection.
     * @retval <=0 Failed to write bytes to the TLS connection.
     */
    int
    smtp_test_seam_ssl_write(SSL *ssl,
                             const void *buf,
                             int num){
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_write_ctr)){
        return -1;
      }
      return SSL_write(ssl, buf, num);
    }
    
    /**
     * Allows the test harness to control when sprintf() fails.
     *
     * @param[in] s      Buffer to store the output contents to.
     * @param[in] format Format string defined in sprintf().
     * @retval >=0 Number of bytes copied to @p s, excluding the null-terminator.
     * @retval <0  Output or formatting error.
     */
    int
    smtp_test_seam_sprintf(char *s,
                           const char *format, ...){
      va_list ap;
      int rc;
    
      if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_sprintf_ctr)){
        errno = ENOMEM;
        return g_smtp_test_err_sprintf_rc;
      }
      va_start(ap, format);
      rc = vsprintf(s, format, ap);
      va_end(ap);
      return rc;
    }
    
    /**
     * Allows the test harness to control when time() fails.
     *
     * @param[out] tloc Buffer to hold the time_t results.
     * @retval >=0 Time in seconds since the Epoch.
     * @retval  -1 Failed to store the time in @p tloc.
     */
    time_t
    smtp_test_seam_time(time_t *tloc){
      if(g_smtp_test_time_custom_ret){
        return g_smtp_test_time_ret_value;
      }
      return time(tloc);
    }