IoT何をいまさら(120) 続Arduino用ソースをWSL2上でGoogle-test

Joseph Halfmoon

前回に引き続き、Arduino環境用に書いた関数をWSL2(Ubuntu)上でGoogle-testしています。多少進捗があったのは「文字列で書いた16進数の計算式」を「評価」して結果を数値で返せるところまでテストが進んだことです。160行ばかりの短いコードだけれども一応インタプリタもどき。今のところ即値の計算のみ。

※「IoT何をいまさら」投稿順Indexはこちら

ArduinoIDEから関数コピーしてテストを続ける

前回WSL2上のUbuntu環境でgoogle-testをCMakeできるようになったので、淡々とテストを続けてます。といってもVScodeで作業しているので、Arduino側のソースをUbuntu側にコピペして走らせるだけですが。まあ、イチイチ実機(ターゲット機はESP32C2番のXiao)のFlashに書き込んで動作を調べるよりかはよっぽど楽。結果、ぽろぽろとエラーを発見し、淡々と修正しとります。

今回のコピペと修正にて、一応以下の機能を果たせそうな関数群ができつつあります(まだ作業中です。)

    1. 文字列で与えた16進数を計算して数値で結果を返せる
    2. 演算子としては今のところ、+、-、*、&(ビットAND)、|(ビットOR)、^(ビットXOR)、~(ビット反転)が使える
    3. 掛け算の優先順位が高く、他は同レベル
    4. マルカッコ”()”で優先順位は変更可能

現時点での「インタプリタ」の被テスト関数ソースが以下に。evalU32exp(char *buf, uint32_t *result) という「仮の」トップ関数あり、bufに文字列へのポインタを渡し、resultに結果格納先のuint32_t型へのポインタを渡して呼び出せば、文字列の内容を評価して、結果の数値をresultに戻します。なお、関数そのものの返り値としてエラーがあればtrueを返します。

#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;
}

uint32_t factorU32(char *buf) {
  uint32_t temp;
  if (buf[currentIndex] != '(') return numberU32(buf);
  currentIndex++;
  if (!nextCh()) {
    errFlag = true;
    errNumber = 2;
    return 0;
  }
  temp = expU32(buf);
  if (buf[currentIndex] != ')') {
    errFlag = true;
    errNumber = 3;
    return 0;
  }
  currentIndex++;
  nextCh();
  return temp;
}

uint32_t termU32(char *buf) {
  uint32_t temp = factorU32(buf);
  while (true) {
    if (buf[currentIndex] == '*') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 4;
        return 0;
      }
      temp *= factorU32(buf);
    } else {
      break;
    }
  }
  return temp;
}

uint32_t expU32(char *buf) {
  uint32_t temp = termU32(buf);
  while (true) {
    if (!nextCh()) {
      break;
    }
    if (buf[currentIndex] == '+') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 5;
        return 0;
      }
      temp += termU32(buf);
    } else if (buf[currentIndex] == '-') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 6;
        return 0;
      }
      temp -= termU32(buf);
    } else if (buf[currentIndex] == '&') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 7;
        return 0;
      }
      temp &= termU32(buf);
    } else if (buf[currentIndex] == '|') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 8;
        return 0;
      }
      temp |= termU32(buf);
    } else if (buf[currentIndex] == '^') {
      currentIndex++;
      if (!nextCh()) {
        errFlag = true;
        errNumber = 9;
        return 0;
      }
      temp ^= termU32(buf);
    } else {
      break;
    }
  }  
  return temp;
}

bool evalU32exp(char *buf, uint32_t *result) {
  currentIndex = 0;
  lastIndex = strlen(buf);
  errFlag = false;
  errNumber = 0;
  nextCh();
  *result = expU32(buf);
  return errFlag;
}
別シリーズ記事からのフィードバック

前回時点の被テスト関数ソースを使い、別シリーズの以下記事にて、gcov(コードカバレッジツール)も掛けてみてます。

オプション沼(13) コードカバレッジを計測する準備、gcc ‐‐coverageオプション

結果、numberU32関数内に未テスト部分が洗い出されたので、以下ではTESTを追加しています。今回追加部分はカバレッジを見ていないので、多分、ザル。またカバレッジを測定して、TESTを追加しなければ。。。

TESTフィクスチャ・コード

今回から「再帰下降型構文解析」っぽい部分に突入したので、大分TESTフィクスチャが長くなりました。「再帰」なのでちょいと気を抜くと戻ってこなくなります。Arduino環境でこれをやるとデバッグ大変ですが、Ubuntu上なら暴走し放題?だあ、と。とりあえずの暫定ソースが以下に。

#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);
    EXPECT_FALSE(errFlag);
    EXPECT_EQ(errNumber, 0);
    strcpy(buf, "FFFFFF80"); //TEST HOLE.
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(numberU32(buf), 0);
    EXPECT_TRUE(errFlag);
    EXPECT_EQ(errNumber, 1);
}

TEST(testrunner, expU32) {
    strcpy(buf, "1234");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x1234);
    EXPECT_EQ(currentIndex, 4);

    strcpy(buf, "1234 + 1");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x1235);
    EXPECT_EQ(currentIndex, 8);

    strcpy(buf, "1234 - 1");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x1233);
    EXPECT_EQ(currentIndex, 8);

    strcpy(buf, "1234 & 4");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x4);
    EXPECT_EQ(currentIndex, 8);

    strcpy(buf, "1234 | 3");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x1237);
    EXPECT_EQ(currentIndex, 8);

    strcpy(buf, "1234 ^ 1231");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x5);
    EXPECT_EQ(currentIndex, 11);

    strcpy(buf, "1 + 1234 * 2");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x2469);
    EXPECT_EQ(currentIndex, 12);

    strcpy(buf, "2 * (1234 + 1)");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x246A);
    EXPECT_EQ(currentIndex, 14);
}

TEST(testrunner, termU32) {
    strcpy(buf, "1234 * 2");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x2468);
    EXPECT_EQ(currentIndex, 8);
}

TEST(testrunner, factorU32) {
    strcpy(buf, "(1234)");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x1234);
    EXPECT_EQ(currentIndex, 6);
}

TEST(testrunner, evalU32exp) {
    uint32_t result;
    strcpy(buf, "1234");
    EXPECT_FALSE(evalU32exp(buf,&result));
    EXPECT_EQ(result, 0x1234);

    strcpy(buf, "2 * (1234 + 1)");
    EXPECT_FALSE(evalU32exp(buf, &result));
    EXPECT_EQ(result, 0x246A);
}
Goolge-testの結果

テスト結果が以下に。多分ザルだけれどもとりあえずテストは通っておると。

gtestResults

次回は、変数への格納と呼び出し?それとも組み込み関数?それにしてもBNFとか書かずに作業していてわかんなくならないか?泥縄。

IoT何をいまさら(119) ESP32C3用のソースをWSL2上でGoolge-test へ戻る

IoT何をいまさら(121) インタプリタもどき、WSL2上でGoogle-testその2 へ進む