Newer
Older
libkc / modules / src / kc_ut.c
/**
 * @file  kc_ut.c
 * @brief 単体テストモジュール
 * @copyright  2003 - 2024  Nomura Kei
 */
#include <stdio.h>
#include <kc_ut.h>
#include <kc_term.h>
#include <kc_assert.h>

// 最大登録関数
#define KC_UT_ENTRY_MAX (16384)
#define KC_UT_OUTPUT stderr

/**
 * テスト用関数リスト。
 */
typedef struct KcUtEntry_
{
    UtType type;
    const char *title;
    void (*func)(void);
} KcUtEntry;

/**
 * テスト管理情報
 */
typedef struct
{
    int test_counter; //!< 実施テスト数
    int ok_counter;   //!< OK 件数
    int ng_counter;   //!< NG 件数
    int func_counter; //!< 登録関数の数
    int test_case;    //!< テストケース数
    int run_index;    //!< 実行関数インデックス
    bool is_ng;       //!< NG が発生したか否かの判定用
    KcUtEntry entry[KC_UT_ENTRY_MAX];
} KcUtInfo;

// プロトタイプ宣言
static void KcUt_assert_handler(const char *msg);
static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void));
static void KcUt_run(KcUt *ut);

// 公開関数

/**
 * 単体テスト用のインスタンスを取得します。
 *
 * @return 単体テスト用の唯一のインスタンス
 */
KcUt *KcUt_get_instance(void)
{
    static KcUt instance;
    static KcUtInfo info = {
        .test_counter = 0,
        .ok_counter = 0,
        .ng_counter = 0,
        .func_counter = 0,
        .run_index = 0,
        .test_case = 0,
        .is_ng = false,
    };
    if (instance._info != &info)
    { // 初回のみ初期化する。
        instance.add = KcUt_add;
        instance.run = KcUt_run;
        instance._info = &info;
    }
    return &instance;
}

// 内部関数

/**
 * 単体テスト用 assert ハンドラ。
 *
 * @param msg メッセージ
 */
static void KcUt_assert_handler(const char *msg)
{
    KcUt *ut = KcUt_get_instance();
    ((KcUtInfo *)ut->_info)->is_ng = true;
    fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg);
    fprintf(stdout, KC_TERM_DEF KC_TERM_CLR);
}

/**
 * テストケースまたは、事前処理/事後処理の関数を追加します。
 *
 * @param ut    KcUT インスタンス
 * @param type  タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER)
 * @param title タイトル
 * @param func  追加する関数
 */
static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void))
{
    KcUtInfo *info = (KcUtInfo *)ut->_info;
    info->entry[info->func_counter].type = type;
    info->entry[info->func_counter].title = title;
    info->entry[info->func_counter].func = func;
    info->func_counter++;
    if (type == UT_TESTCASE)
    {
        info->test_case++;
    }
}

/**
 * テストケースを実行します。
 *
 * @param ut    KcUT インスタンス
 */
static void KcUt_run(KcUt *ut)
{ // assert_handler を単体試験用ハンドラに切り替えておく。
    assert_handler = KcUt_assert_handler;

    const char *result;
    KcUtInfo *info = (KcUtInfo *)ut->_info;
    // KcMemory_start(true);
    for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++)
    {
        info->is_ng = false;
        info->entry[info->run_index].func();
        if (info->entry[info->run_index].type == UT_TESTCASE)
        { // テストケースの場合のみエラー判定&結果表示する。
            info->test_counter++;
            if (!info->is_ng)
            { // テスト成功
                info->ok_counter++;
                result = KC_TERM_GRN "OK" KC_TERM_DEF;
            }
            else
            { // テスト失敗
                info->ng_counter++;
                result = KC_TERM_RED "NG" KC_TERM_DEF;
            }
            // 実行結果表示
            printf(
                KC_TERM_BLD KC_TERM_CYN
                "[No.%05d] %-65s [   %s" KC_TERM_CYN "   ]\n" KC_TERM_DEF KC_TERM_CLR,
                info->test_counter,
                info->entry[info->run_index].title,
                result);
            fprintf(stderr, KC_TERM_DEF KC_TERM_CLR);
        }
    }
    printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n");
    printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter);
    printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter);
    printf(KC_TERM_CYN " Total   : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter);
    printf("\n");
}