2024-09-11

「低レイヤを知りたい人のためのCコンパイラ作成入門」第四回勉強会まとめ

今回の学習範囲

自動テストの作成~makeによるビルドまで

目標

テストをシェルスクリプトで書いて、そのテストコードが正しく通ることを確認する

自動テストの作成

#!/bin/bash
assert() {
    expected="$1"
    input="$2"

    ./9cc "$input" > tmp.s
    cc -o tmp tmp.s
    ./tmp
    actual="$?"

    if [ "$actual" = "$expected" ]; then
        echo "$input => $actual"
    else
        echo "$input => $expected expected, but got $actual"
        exit 1
    fi
 }

assert 0 0
assert 42 42

echo OK

上記の内容でtest.shを作成します。

コードの説明

9ccの結果をアセンブルし、実際の結果($actual)を期待されている値($expected)と比較するということを行い、0と42がどちらも正しくコンパイルできることを確認するためのテストです。

  1. assert関数は引数を二つ受け取ります。
  2. 入力した値をそのまま返し、$actualに入れます。
    ./9cc "$input" > tmp.s
     cc -o tmp tmp.s
     ./tmp
     actual="$?"
    
  3. 期待する値=$expected(第一引数)と入力した値=$input(第二引数)の実行結果の$actualが等しければ真となります。

test.shに実行権限付与

作成したtest.shファイルは現在以下のようになっています。
オーナーの権限(ファイルの持ち主)は読み取り(Read)と書き込み(Write)のみで実行権限はありません。
権限はls -alFコマンドで確認できます。

-rw-r--r--    1 kazu  staff    319  9  6 17:52 test.sh

ですのでchmod a+x test.shを実行して実行可能にします。

x(execute)追加されて実行可能となりました。

-rwxr-xr-x    1 kazu  staff    319  9  6 17:52 test.sh*

Macを使用している方へ

同じディレクトリにMakefileというファイル名で作成してください。
毎回dockerコマンドを打つのは正直大変なので、Makefileに記述してコマンドを登録し自動化します。

$ docker run -it -v $HOME/9cc:/9cc -w /9cc compilerbook 

Makefile内

CFLAGS=-std=c11 -g -static

9cc: 9cc.c
    docker run -it -v ${CURDIR}:/9cc -w /9cc compilerbook cc -o 9cc 9cc.c

test: 9cc
    docker run -it -v ${CURDIR}:/9cc -w /9cc compilerbook ./test.sh

clean:
    rm -f 9cc *.o *~ tmp*

.PHONY: test clean
  • ${CURDIR}は現在のディレクトリの現在のパスを取得します。
  • /9cc -w /9ccはdockerのコンテナ内のディレクトリを取得します。

これでmake testと打つだけでテストを実行できるようになりました!

テストの実行

正しいとき

$ make test
docker run -it -v /Users/kazu/9cc:/9cc -w /9cc compilerbook ./test.sh
0 => 0
42 => 42
OK

エラーを起こさせてみます。assertに渡す引数を以下のように変更します。

assert 0   0
assert 42 123

実行してみましょう。1つ目のassert関数は通りましたが、2つ目のassert関数は偽となりました。

make test
docker run -it -v /Users/kazu/9cc:/9cc -w /9cc compilerbook ./test.sh
0 => 0
123 => 42 expected, but got 123

シェルスクリプトを記述する上での注意

シェルスクリプトはスペースにとても敏感です。
以下のような正しいコードで実行すると '[' 0 = 0 ']'となりますが、

 if [ "$actual" = "$expected" ]; then

$expectedの右のスペースをなくすと '[' 0 = '0]'となり、0がシングルクオーテーションに含まれてしまいます。

つまり、0]というコマンドとして認識されてしまうためエラーとなります。
スペースや誤字に気をつけましょう。

やったことまとめ

  • シェルスクリプトでテストコードを記述し、挙動の確認をしました。
  • Makefileに実行したいコマンドを登録し自動化を行いました。

所感&メモ

自動テストというものは一発で動かして機械的に比較できるくらい簡単なものでいいということでした。難しいことは考えずにとりあえずテストを書くことが大切だということでした。