前回から少し間が空きました。「車輪の再発明」的な作業を続けてますが、これはESP32C3 Xiao上で「セルフ開発支援環境」を作らんとの野望からです。セルフ環境でIOレジスタの値を吟味したり設定変えたり、いろいろやりて~と。機械語モニタ的な?Arduino環境でプログラム作成ですが、ここに来てメンドくなってきた。
※「IoT何をいまさら」投稿順Indexはこちら
「なんちゃって」セルフ環境
「お求めやすい」ボードは大好きです。しかしその開発環境については業務用の開発ツール等に比べると貧弱と言わざるを得ません。お求めやすいボードでもデバッグI/Fまで内容しているものがありますが、今回ターゲットとしておるESP32C3 Xiaoは超小型機なのでそれはないです。ハードを追加すればデバッガを載せてブレーク掛けたりすることくらいは出来なくもないようです。しかし、私としては、いちいちプログラム書いてFlashに書き込みせずに、IOレジスタの値を吟味して値を変更し、チョコチョコっと周辺回路動作をエクササイズしたいです。大昔のマイコン環境にあった機械語モニタ的なやつね。そいつらは皆セルフで動作してました。
ESP32C3はメモリもそれなりにデカく、32ビットRISC-V搭載っす。昔のマイコンに比べたら段違いの高性能。そこで機械語モニタに加えて御勝手スクリプト言語というかシェル的な機能も搭載して~と考えました。ついては「再帰下降型」の構文解析(もどき)を実装していく予定。以前「再帰下降型」でコンパイラ作ったことがありますが、今回はインタプリタとな。自己満足なお楽しみです。
そこでArduino IDEで「再帰下降型」書き始めたのですが、途中で手が止まってしまいました。これをビルドして ESP32C3上でデバッグするとなったら何回Flashに書き込んだらよいのか?(多分飛んでもない回数だな。)ビルドと書き込みには毎回それなりの時間が掛かります。待ち時間長。その上デバッグしずらいっす。一方今作っているようなルーチンは別にマイコン上でなくても処理できるような部分ばかりです。
パソコン上でできるところまで作って、それからマイコンに載せるか
という方向に方針転換しました。Arduino環境上にもテストフレームワークがあるようなのですが、マイコンに書き込んで動作させるタイプしか見当たらなかったです。そこでマイコン(Arduino)でもパソコンでも「同じように動くだろう」という部分はパソコン上のGoogle Testでユニットテストを行って、Arduino環境に持ち込むことにいたしました。
Googleテスト
Googleテストは定番のテストフレームワークです。そういう割に最後に使ったのは一年以上前、以下の別シリーズ投稿でです。
ソフトな忘却力(14) ラズパイでgtest、CMakeLists.txt書くだけでOK
上記ではラズパイ上でしたが、今回はWSL2(Ubuntu 20.04LTS)使用です。上記で嬉しかったのは、CMake使う場合、Googleテストなどインストールしてない環境でも、CMakeList.txtさえ書けば、GoogleテストのインストールからCMakeが面倒みてくれる、というところでありました。その辺の説明が以下に。
Quickstart: Building with CMake
さて今回記述したCMakeList.txtが以下に。
cmake_minimum_required(VERSION 3.14) project(arduinotest) set(CMAKE_CXX_STANDARD 11) include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) enable_testing() add_executable( testrunner testrunner.cpp undertest.cpp ) target_link_libraries( testrunner gtest_main ) include(GoogleTest) gtest_discover_tests(testrunner)
なお、urlのところに書かれているアドレスは一年以上前の別シリーズ投稿の時点のものなので古いです。最新版は多分違う筈。
後で arduino環境に持ち込むソースは undertest.cpp に入ってます。それに対してユニットテスト用の記述はtestrunner.cppに入ってます。
まず今回部分的にテストした undertest.cppが以下に。
#include "undertest.hpp" extern char buf[]; //--- Under test ------------------- int currentIndex; int lastIndex; bool errFlag; bool errNumber; bool nextCh() { while (isSpace(buf[currentIndex])) { currentIndex++; } if (currentIndex >= lastIndex) { return false; } return true; } char toUpper(char arg) { if ((arg >= 'a') && (arg <= 'z')) { return arg - 32; } return arg; } bool toHex(char arg, uint32_t *res) { char work = toUpper(arg); if ((work >= '0') && (work <= '9')) { *res = (uint32_t)(work - '0'); return true; } if ((work >= 'A') && (work <= 'F')) { *res = (uint32_t)(work - 'A' + 10); return true; } return false; } uint32_t numberU32(char *buf) { uint32_t sum, temp; bool bitNotFlag = false; if (buf[currentIndex] == '~') { bitNotFlag = true; currentIndex++; nextCh(); } if (! isdigit(buf[currentIndex])) { //first character must be 0-9 errFlag = true; errNumber = 1; return 0; } sum = (uint32_t)(buf[currentIndex++] - '0'); while (nextCh() && toHex(buf[currentIndex], &temp)) { sum = (sum << 4) + temp; currentIndex++; } return bitNotFlag ? ~sum : sum; }
上記の被テスト関数などをtestrunner側で呼び出すためのヘッダファイル undertest.hppが以下に。
#include <stdio.h> #include <stdint.h> #include <string.h> #include <ctype.h> #define isSpace(arg) isspace(arg) extern int currentIndex; extern int lastIndex; extern bool errFlag; extern bool errNumber; bool nextCh(); char toUpper(char arg); bool toHex(char arg, uint32_t *res); uint32_t numberU32(char *buf);
そして最後に、実際にテストをする部分が以下に。例によって手抜きだけれども。
#include <gtest/gtest.h> #include "undertest.hpp" char buf[256]; TEST(testrunner, nextCh) { strcpy(buf, " 1234\t 567"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_TRUE(nextCh()); EXPECT_EQ(currentIndex, 2); currentIndex += 4; EXPECT_TRUE(nextCh()); EXPECT_EQ(currentIndex, 8); } TEST(testrunner, toUpper) { EXPECT_EQ(toUpper('a'), 'A'); EXPECT_EQ(toUpper('z'), 'Z'); EXPECT_EQ(toUpper('1'), '1'); EXPECT_EQ(toUpper('X'), 'X'); EXPECT_EQ(toUpper('/'), '/'); } TEST(testrunner, toHex) { uint32_t work; EXPECT_TRUE(toHex('a', &work)); EXPECT_EQ(work, 10); EXPECT_TRUE(toHex('0', &work)); EXPECT_EQ(work, 0); EXPECT_TRUE(toHex('9', &work)); EXPECT_EQ(work, 9); EXPECT_TRUE(toHex('F', &work)); EXPECT_EQ(work, 15); EXPECT_FALSE(toHex('.', &work)); EXPECT_FALSE(toHex('G', &work)); EXPECT_FALSE(toHex('x', &work)); EXPECT_FALSE(toHex('%', &work)); } TEST(testrunner, numberU32) { strcpy(buf, "1234"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0x1234); EXPECT_EQ(currentIndex, 4); strcpy(buf, "0FF"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0xFF); EXPECT_EQ(currentIndex, 3); strcpy(buf, "~0FFFFFF80"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0x7F); EXPECT_EQ(currentIndex, 10); }
Googleテストしてみる
テストはたったの3ステップ。-S . でソースはここよ、と教えて、-B で buildディレクトリを掘ってね、とお願い。
初回は、Googleテスト本体を持ち込んだりするのでいろいろ大変そうですが、2回目以降は一撃。
第2ステップでビルド。裏でninjaが呼ばれているのか? make? 知らんけど。
buildディレクトリの中にtestrunnerが出来ているのでそいつを走らせます。
上記はね、一撃でPASSしているように見えるけど、実際には赤字のFailが結構あったのよ。でもまFlashに書き込んでデバッグするよりは修正速いっす。
次回は本格的に「再帰下降型」か?自分で決めた文法を忘れそうだから、BNFでも書いておくか?迷走だな。