Qiita · Unity 开发社区· 来源· hk816·· 3 小时前AI 评分1
基于 Unity 测试框架的 SSD L2P 映射更新原子性与健壮性验证设计
【テスト】SSDの文鎮化をテストで防げ!「L2Pマッピング更新」の完全網羅テスト設計(Unity編)
正文 · AI 翻译
转到点赞用户列表
0
在 X (Twitter) 上分享
在 Facebook 上分享
添加到 Hatena Bookmark
删除文章
已删除的文章无法恢复。
本文的草稿也会被删除。
您确定要删除这篇文章吗?
\n🚀 用测试防止 SSD 变砖!「L2P 映射更新」的全面测试设计(Unity 篇)
各位嵌入式工程师,在 SSD 固件中,“数据损坏”的瞬间是什么时候呢?
那不是 NAND 闪存寿命耗尽的时候。而是“正在向 NAND 写入新数据时电源突然断开,而管理表(L2P)却先被更新时”。
为了预防这种情况,证明正确实现了“Write-then-Update(事务的原子性)”的单元测试将在本次介绍。
利用 Unity(面向 C 语言的测试框架)和 Mock,故意引发“NAND 写入失败(Program Fail)”这一硬件的最坏行为,从而 100% 证明固件的健壮性!
\n💡 测试设计的要点(面试中能谈到的测试精髓)
在本测试组中,不仅旨在提高代码覆盖率(Coverage),还专门针对存储特有的“边缘情况”进行测试。
- 原子性的完整证明(异常场景)
让模拟函数HAL_NandProgramPage返回错误(false)。在此“写入失败”之后,验证 DRAM 上的 L2P 表绝对未被重写为新的 PBA,而是保持旧的 PBA(回滚保护)。 - 坏块隔离(GBB 注册)的验证(异常场景)
由于向 NAND 写入失败的物理块很可能在物理上已损坏,因此确认该块是否立即在FTL_MarkBlockAsGbb中被隔离(注册为坏块)。 - 覆盖写入时的“旧 PBA 无效化”验证(正常场景)
针对首次写入(Unmapped)和第二次及以后的覆盖写入(Overwrite)进行分支测试。证明仅在覆盖写入成功时,确保对存储有旧数据的旧 PBA 调用FTL_InvalidatePba,使其成为垃圾回收(GC)的对象。 - 防御性编程的验证(异常场景)
验证当输入 NULL 指针或超出规格上限的 LBA(越界)时,会在引发内存破坏之前立即被拒绝。
💻 实现代码
#include "unity.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
/* テスト対象のCファイルを直接インクルード(ホワイトボックステスト用) */
#include "ftl_l2p_update.c"
/* --- モック用制御・状態記録変数 --- */
static bool g_mock_allocate_success = true;
static uint32_t g_mock_next_free_pba = 0U;
static bool g_mock_program_success = true;
static uint32_t g_call_program = 0U;
static uint32_t g_call_invalidate = 0U;
static uint32_t g_last_invalidated_pba = FTL_UNMAPPED_PBA;
static uint32_t g_call_gbb = 0U;
static uint32_t g_last_gbb_pba = FTL_UNMAPPED_PBA;
/* --- 外部HAL/FW関数のダミー実装 (モック) --- */
bool FTL_AllocateFreePba(uint32_t* p_out_pba) {
if (g_mock_allocate_success) {
*p_out_pba = g_mock_next_free_pba;
g_mock_next_free_pba++;
return true;
}
return false;
}
bool HAL_NandProgramPage(uint32_t pba, const uint8_t* p_data, const NAND_OobMetadata_t* p_oob) {
g_call_program++;
return g_mock_program_success;
}
void FTL_InvalidatePba(uint32_t pba) {
g_call_invalidate++;
g_last_invalidated_pba = pba;
}
void FTL_MarkBlockAsGbb(uint32_t pba) {
g_call_gbb++;
g_last_gbb_pba = pba;
}
/* --- テストのセットアップ --- */
void setUp(void) {
g_mock_allocate_success = true;
g_mock_next_free_pba = 1000U; /* テスト用の開始PBA */
g_mock_program_success = true;
g_call_program = 0U;
g_call_invalidate = 0U;
g_last_invalidated_pba = FTL_UNMAPPED_PBA;
g_call_gbb = 0U;
g_last_gbb_pba = FTL_UNMAPPED_PBA;
/* L2Pテーブルを未マッピング状態で初期化 */
for (uint32_t i = 0U; i < FTL_MAX_SUPPORTED_LBA; i++) {
s_l2p_table[i] = FTL_UNMAPPED_PBA;
}
}
void tearDown(void) {}
/* =========================================================
* FTL_WriteAndMap のテストケース
* ========================================================= */
/* 1. 異常系: 引数エラー(NULLポインタと境界値) */
void test_FTL_WriteAndMap_InvalidArgs_ShouldReturnFalse(void) {
uint8_t data[4096] = {0};
TEST_ASSERT_FALSE_MESSAGE(FTL_WriteAndMap(0U, NULL), "データポインタがNULLの場合はfalseを返すこと");
TEST_ASSERT_FALSE_MESSAGE(FTL_WriteAndMap(FTL_MAX_SUPPORTED_LBA, data), "上限以上のLBAを指定した場合はfalseを返すこと");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_program, "無効な要求ではNANDアクセスを行わないこと");
}
/* 2. 異常系: 空き物理ブロックが枯渇している場合のアロケーション失敗 */
void test_FTL_WriteAndMap_AllocationFails_ShouldReturnFalse(void) {
uint8_t data[4096] = {0};
g_mock_allocate_success = false; /* SSDフル等の理由でPBA取得失敗をシミュレート */
bool result = FTL_WriteAndMap(10U, data);
TEST_ASSERT_FALSE_MESSAGE(result, "PBA確保失敗時はfalseを返すこと");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_program, "NAND Programは呼ばれないこと");
}
/* 3. 正常系: 未マッピング領域への新規書き込み */
void test_FTL_WriteAndMap_FirstWrite_ShouldMapSuccessfully(void) {
uint8_t data[4096] = {0xAA};
uint32_t target_lba = 100U;
uint32_t expected_pba = 1000U; /* モックが返す最初のPBA */
bool result = FTL_WriteAndMap(target_lba, data);
TEST_ASSERT_TRUE_MESSAGE(result, "書き込み成功時はtrueを返すこと");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_program, "NAND Programが1回呼ばれること");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_pba, s_l2p_table[target_lba], "L2Pテーブルが新しいPBAへ更新されていること");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_invalidate, "初回書き込みのため、Invalidate(旧PBAの破棄)は呼ばれないこと");
}
/* 4. 正常系: 既存領域への上書き(Overwrite)とガベージコレクション準備 */
void test_FTL_WriteAndMap_Overwrite_ShouldMapAndInvalidateOldPba(void) {
uint8_t data[4096] = {0};
uint32_t target_lba = 200U;
/* 事前準備: すでにPBA 500 にマッピングされている状態を作る */
s_l2p_table[target_lba] = 500U;
uint32_t expected_new_pba = 1000U;
bool result = FTL_WriteAndMap(target_lba, data);
TEST_ASSERT_TRUE_MESSAGE(result, "上書き成功時はtrueを返すこと");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_new_pba, s_l2p_table[target_lba], "L2Pテーブルが新しいPBAへ更新されていること");
/* ガベージコレクション用連携の検証 */
TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_invalidate, "上書き時は、旧PBAのInvalidate関数が必ず呼ばれること");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(500U, g_last_invalidated_pba, "Invalidate対象が正しく「上書き前の旧PBA」であること");
}
/* 5. 異常系(最重要): NAND書き込み失敗時のアトミック性証明と不良ブロック隔離 */
void test_FTL_WriteAndMap_ProgramFails_ShouldNotUpdateL2pAndMarkGbb(void) {
uint8_t data[4096] = {0};
uint32_t target_lba = 300U;
/* 事前準備: すでにPBA 600 にマッピングされている状態 */
s_l2p_table[target_lba] = 600U;
/* NANDへのハードウェア書き込みが失敗(Program Fail)する最悪のシナリオ */
g_mock_program_success = false;
uint32_t expected_failed_pba = 1000U;
bool result = FTL_WriteAndMap(target_lba, data);
TEST_ASSERT_FALSE_MESSAGE(result, "NAND書き込み失敗時はfalseを返すこと(フェイルセーフ)");
/* 【トランザクション保護の証明】 */
TEST_ASSERT_EQUAL_UINT32_MESSAGE(600U, s_l2p_table[target_lba], "【最重要】書き込み失敗時は絶対にL2Pを更新せず、古い正常なデータ(PBA 600)を死守すること");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_invalidate, "エラー時は旧データのInvalidateを行わないこと(旧データを保護)");
/* 【異常ブロックの隔離】 */
TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_gbb, "書き込みに失敗したブロックは後発不良(GBB)登録関数が呼ばれること");
TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected_failed_pba, g_last_gbb_pba, "GBB登録対象が、書き込みに失敗した新しいPBAであること");
}
查看点赞用户列表
0
注册新用户并更便捷地使用 Qiita
- 获取符合您需求的文章
- 高效回顾有用信息
- 支持深色主题
转到喜欢该文章的用户列表
0
删除文章
已删除的文章无法恢复。
本文的草稿也将被删除。
确定要删除此文章吗?
来源:Qiita · Unity 开发社区 · qiita.com