RL78マイコンの自動テスト環境を作る その1：とりあえず動かす (gdb sim + e2studio + Unity)

# はじめに
## 概要
書籍『[テスト駆動開発による組み込みプログラミング](https://www.oreilly.co.jp/books/9784873116143/)』を読み始めたことをきっかけに、業務でよく触るRenesasのRL78マイコン向けのテスト環境構築を目指すことにしました。
とりあえずビルド時のテスト実施・結果表示ができたので、そこまでの手順を本記事にまとめます。

## この記事でやること
**"とりあえず"**、ビルド時に命令レベルのシミュレータで自動テストが実施されるようにする

1. テストフレームワーク Unityを、e2studioで作成するRL78マイコン向けソフトのプロジェクトに入れる
2. ビルド & e2studioでのシミュレータ実行で、テスト結果が確認できるようにする
3. e2studioのビルド時に自動テストが走るようにする

# 環境
## 使用する環境
* OS: Windows 11
* IDE: e2studio 2023-04 (23.4.0)
[https://www.renesas.com/jp/ja/software-tool/e-studio](https://www.renesas.com/jp/ja/software-tool/e-studio)
* コンパイラ：CC-RL v1.12.00
* GDB：GNU gdb (GDB) 7.8.2-20211130-Build_1  
（e2studioのインストール時についてきたものを使います）
* テストフレームワーク： Unity   
[https://github.com/ThrowTheSwitch/Unity](https://github.com/ThrowTheSwitch/Unity)

## 準備
### プロジェクト作成
使用するRL78のプロジェクトを作成します。
RL78のプロジェクトであればなんでもよいはずです。今回は、以下の評価ボードで使用されており、実機上のデバッグ時も使用できる、RL78/G14ファミリ R5F104MLマイコンでプロジェクトを作成します。
また今回は今回はコード生成ツールも使用します。

### gdbの確認
テストの実行にあたり、デバッグツールGDBをrl78向けにカスタマイズした、`rl78-elf-gdb`を使用します。
同ツールの場所を確認します。デフォルト設定でe2studioをインストールした僕の場合、`C:\Users\[ユーザ名]\.eclipse\com.renesas.platform_1435879475\DebugComp\RL78`に配置されていました。
また、`rl78-elf-gdb`の実行にあたり、e2studioと一緒にインストールされるPython 2.7.2が必要（らしい）です。
こちらも場所を確認します。僕の場合、`C:\Renesas\e2_studio\eclipse\runtimes\python\2.7.12_x86`にありました。
上記のパスを使用して、`rl78-elf-gdb`が正しく動作することを確認します。

```
>set PYTHONPATH=C:\Renesas\e2_studio\eclipse\runtimes\python\2.7.12_x86\Lib
>set Path=C:\Renesas\e2_studio\eclipse\runtimes\python\2.7.12_x86;%Path%
>C:\Users\[ユーザ名]\.eclipse\com.renesas.platform_1435879475\DebugComp\RL78\rl78-elf-gdb --version

GNU gdb (GDB) 7.8.2-20211130-Build_1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-mingw32 --target=rl78-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
```
このようにバージョン情報が表示されればOKです

##  Unityの導入
### ファイルのコピー
以下の記事を参考に、cloneもしくはダウンロードしたUnityの各ソースを、e2studioのプロジェクトにコピーして追加します。

[RXマイコンで、Unityによる単体テスト環境を作ってみた（後編） - /Test_you](https://sonoka-gi.hatenablog.jp/entry/2019/06/24/001857)

まず、以下のようなフォルダ構成になるよう、`tests`および`unity`フォルダを作成します。
```
[プロジェクトルート]
    - src
        - tests
        - unity
```
次に、以下に示す`.c`および`.h`ファイルを`unity`フォルダにコピーします。

```
Unity-master
    - src
        - unity.c
        - unity.h
        - unity_internals.h
    - extras
        - fixture
            - src
                - unity_fixture.c
                - unity_fixture.h
                - unity_fixture_internals.h
        - memory
            - src
                - unity_memory.c
                - unity_memory.h
```

### テスト用ファイルの作成
続いて、同じく[RXマイコンで、Unityによる単体テスト環境を作ってみた（後編） - /Test_you](https://sonoka-gi.hatenablog.jp/entry/2019/06/24/001857) を参考に、テストランナーとテストケースを作成します。

```src/tests/Tests.c

#include "../unity/unity_fixture.h"
#include "../r_cg_macrodriver.h"
#include "../r_cg_port.h"

TEST_GROUP(Test0);

// テストケース前処理
TEST_SETUP(Test0)
{
}

// テストケース後処理
TEST_TEAR_DOWN(Test0)
{
}

// 必ず失敗するテストケース
TEST(Test0, AlwaysFail)
{
    TEST_FAIL_MESSAGE("This test always fails.");
}

// テストグループにテストケースをインストール
TEST_GROUP_RUNNER(Test0)
{
    RUN_TEST_CASE(Test0, AlwaysFail);
}


```

```src/tests/AllTests.c

#include "../unity/unity_fixture.h"

// 実行するテストケースを記述
void RunAllTests(void)
{
    RUN_TEST_GROUP(Test0);
}

```

### dprintf用の関数を作成
RXマイコンの場合、`printf`によるエミュレータのコンソール出力が可能ですが、RL78マイコンの場合は不可能です。
そこで、コンパイル済みバイナリの実行はgdbのシミュレータを、テスト結果などの出力はgdbのdynamic printfを使用します。
dynamic printfのブレークを設置するための関数を以下のように用意します。

（ついでに、テスト終了時を検出するブレークを張るための関数も用意します）

```src/tests/myprintf.c

#include "./myprintf.h"

// 文字列出力用バッファのサイズ
#define CHARBUF_SIZE 100

// 文字列出力用バッファの定義
// ヌル文字を考慮し、SIZE+1とする
static volatile char charBuf[CHARBUF_SIZE+1];
// バッファ用インデックスの定義
unsigned int idxCharBuf = 0;

// dynamic printf用 文字（列）出力関数
// dynamic printfの呼び出し回数を抑えるため、バッファフルもしくは改行コードで出力
void myPrintf(char c)
{
    // charの格納とインデックスのインクリメント
	charBuf[idxCharBuf] = c;
	idxCharBuf ++;
    // バッファフルもしくは改行コード入力の場合
	if ( (idxCharBuf == CHARBUF_SIZE) || (c == '\n'))
	{
        // 末端にヌル文字を挿入
		charBuf[idxCharBuf] = '\0';
        // インデックスを0に戻す
		idxCharBuf = 0;   //※この処理にdprintfを配置
	}
}

// テスト終了時に呼び出す関数（gdbのコンソールに操作を戻すため）
void onTestEnd(void)
{
	idxCharBuf = 0;
}

```

```src/tests/myprintf.h
#ifndef TESTS_MYPRINTF_H_
#define TESTS_MYPRINTF_H_



#endif /* TESTS_MYPRINTF_H_ */

void myPrintf(char c);
void onTestEnd(void);

```

unityによる出力がこの`myPrintf`を通るよう、プリプロセッサの定義を変更します。

```src/unity/unity_internals.h
...
/*-------------------------------------------------------
 * Output Method: stdout (DEFAULT)
 *-------------------------------------------------------*/
#ifndef UNITY_OUTPUT_CHAR
  /* Default to using putchar, which is defined in stdio.h */
  #include <stdio.h>
#include "../tests/myprintf.h"                     //追加
  #define UNITY_OUTPUT_CHAR(a) (void)myPrintf(a)   //変更
#else
...
```

最後に、テストが実行されるよう`main`関数を変更します。

```src/r_main.c
...
/* Start user code for include. Do not edit comment generated here */
#include "unity/unity_fixture.h"
#include "tests/myprintf.h"
/* End user code. Do not edit comment generated here */
...
void main(void)
{
    R_MAIN_UserInit();
    /* Start user code. Do not edit comment generated here */
    int argc = 2;
    const char *argv[] = {"program name", "-v"};
    extern void RunAllTests(void);
    // テスト実行
    UnityMain(argc, argv, RunAllTests);
    // テスト実行終了
    onTestEnd();
    while(1){}
    /* End user code. Do not edit comment generated here */
}

```

ここまでできたら、一度正しくunityの導入ができているかを確認します。
プロジェクトをビルドし、シミュレータでデバッグ実行してください。

自動的にmain関数の入り口でブレークするはずなので、以下のようにdynamic printfを配置してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/4b736bc9-eede-56c0-c4f5-4805fd2f0215.png)

```
"%s",charBuf
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/34677087-df3f-000a-8735-e84621525e8e.png)

ブレークポイント設置後に実行再開すると、Debugger Consoleに、以下のようにunityテスト結果が出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/8f600bde-43d9-f95c-7f69-30b062ef90de.png)
これでunityの導入はOKです。

## ビルド時の自動テスト
最後に、e2studioでのビルド時に自動テストが走り、先ほどのようなテスト結果が出力されるようにします。

gdbのシミュレータを実行するためのバッチファイルおよびgdbのコマンドファイルを新たに作成し、適当な場所に配置します。
（今回はプロジェクトのルートに配置します。）

```autotest.bat
set PYTHONPATH=C:\Renesas\e2_studio\eclipse\runtimes\python\2.7.12_x86\Lib
set Path=C:\Renesas\e2_studio\eclipse\runtimes\python\2.7.12_x86;%Path%
rl78-elf-gdb.exe --silent %1\HardwareDebug\test_test.x < %1\autorun.gdbcmd > %1\autotest_log.txt
type %1\autotest_log.txt
findstr "OK" %1\autotest_log.txt
```

※注1: `PYTHONPATH`および`Path`にセットするそれぞれのパスは、上部「gdbの確認」で調べたパスを使用してください。
※注2: プロジェクトの絶対パスはe2studioからのコマンド実行時に引数として渡します。
※注3: テスト失敗時にビルドエラーが表示されるよう、gdbでのテスト実施後に`findstr`でエラーレベルを設定しています。

```autotest.gdbcmd
target sim
load
dprintf C:/Users/[ユーザ名]/e2_studio/workspace/test_test/src/tests/myprintf.c:23, "%s" ,charBuf
b onTestEnd
shell cls
r
q
```
※注1: 先ほどdynamic printfを仕掛けた場所と同じところでdprintfするよう、フルパス+行数 で場所を指定しています。相対パスにしたい・・・
※注2: 先ほどと違い、`onTestEnd`関数にブレークを張っています。これにより、テスト終了時にgdbコンソールに操作を戻すようにしています。

最後に、e2studioプロジェクトのビルドステップに、このバッチを実行するよう設定します。
`プロジェクト ＞ プロパティ ＞ C/C++ ビルド ＞ 設定 ＞ ビルド・ステップ`
「ビルド後のステップ」を以下にします。
```
${workspace_loc:/${ProjName}}\autotest.bat ${workspace_loc:/${ProjName}}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/2d5644ad-1067-5194-1716-fb571e989b59.png)

ここまでできたら、e2studioでプロジェクトをビルドします。
以下のように、テスト結果が表示され、テスト失敗を理由にビルドエラーになれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/cf199e1e-edd6-b4b1-0562-ea1a1fc8c6f1.png)

`FAIL_MESSAGE`を消して、テストOKであればビルドOKになることも確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166634/b1791f3e-198f-13b8-fa63-e79b3cc2752a.png)

## おわりに
とりあえず動いたけど、ちゃんと使えるようにいろいろ改善したいですね・・・・

# 参考
* [テスト駆動開発による組み込みプログラミング](https://www.oreilly.co.jp/books/9784873116143/)
* [RXマイコンで、Unityによる単体テスト環境を作ってみた（後編）](https://sonoka-gi.hatenablog.jp/entry/2019/06/24/001857) 
* [e² studio での CUnit の使用方法（GCC for RX）](https://www.renesas.com/us/en/document/apn/how-use-cunit-e-studio-gcc-rx?language=ja)
* [e2studioのデバッグ時のGDBコマンドについて](https://community-ja.renesas.com/cafe_rene/forums-groups/tools/f/forum21/7931/e2studio-gdb)
* [TDDによるマイコンのLチカ開発(1)](https://qiita.com/iwatake2222/items/396959d1d7dffee479f7)