「Effective Debugging」(D. Spinellis et al.、オライリー・ジャパン、2017)では、
CやC++などの低級言語で開発したプログラムのテストを自動化する手法として、APIバインディングを作成しスクリプト言語によるテストシナリオの実行を提案しています。
この本ではスクリプト言語としてLuaを用いたケースを紹介していますが、正直Luaはマイナー言語であり、このためにわざわざ学習するのは面倒です。
そこで、テストシナリオのためのスクリプト言語としてLuaでなくPythonを用いた場合の手法を紹介します。
Lua版
以下に本で紹介されていたLua版によるテストシナリオのプログラムを引用します。
# include <math.h>
# include <lua.h>
# include <luaxlib.h>
# include <lualib.h>
// Luaで処理する関数
static int l_sin(lua_State *L) {
double value_as_number = luaL_checknumber(L, 1);
// 関数を呼び出し、結果を返す
lua_pushnumber(L, sin(value_as_number));
return 1; // 単一の結果
}
static int l_cos(lua_State *L) {
double value_as_number = luaL_checknumber(L, 1);
lua_pushnumber(L, cos(value_as_number));
return 1;
}
static int l_tan(lua_State *L) {
double value_as_number = luaL_checknumber(L, 1);
lua_pushnumber(L, tan(value_as_number));
return 1;
}
int main() {
// Luaの設定
lua_pushfunction(L, l_sin);
lua_setglobal(L, "lsin");
lua_pushfunction(L, l_cos);
lua_setglobal(L, "lcos");
lua_pushfunction(L, l_tan);
lua_setglobal(L, "ltan");
// デバッグファイルをロードして実行
luaL_dofile(L, "debug.lua");
puts("Done");
return 0;
}
epsilon = 1
errors = 0
while epsilon > 0 and errors < 2 do
for theta = 0, 2 * math.pi, 0.1 do
diff = lsin(theta) / lcos(theta) - ltan(theta)
if (math.abs(diff) > epsilon) then
print(epsilon, theta, diff)
errors = errors + 1
end
end
epsilon = epsilon / 10
end
このプログラムを実行すると、以下のように出力されます。
1e-14 4.7 1.4210854715202e-14
1e-15 1.5 1.7763568394003e-15
1e-15 4.7 1.4210854715202e-14
Done
このテストシナリオは、正接関数の定義(tan=sin/cos)に基づいて、3つの関数tan、sin、cosの計算結果が正しいかを確認しています。
結果としては、誤差は十分小さいので、3つの関数の実装はおそらく正しいでしょう。
プログラムのコンパイルについては本に書いてあるのですが、私の環境で試してもコンパイルに成功しなかったので割愛します。
Python版
私の実行環境はWSL上のUbuntu16.04LTSです。
# include <Python.h>
# include <math.h>
static PyObject* myprog_sin(PyObject *self, PyObject *args) {
double x;
if (!PyArg_ParseTuple(args, "d", &x))
return NULL;
double y = sin(x);
return PyFloat_FromDouble(y);
}
static PyObject* myprog_cos(PyObject *self, PyObject *args) {
double x;
if (!PyArg_ParseTuple(args, "d", &x))
return NULL;
double y = cos(x);
return PyFloat_FromDouble(y);
}
static PyObject* myprog_tan(PyObject *self, PyObject *args) {
double x;
if (!PyArg_ParseTuple(args, "d", &x))
return NULL;
double y = tan(x);
return PyFloat_FromDouble(y);
}
static PyMethodDef MyprogMethods[] = {
{"sin", myprog_sin, METH_VARARGS, "Sine"},
{"cos", myprog_cos, METH_VARARGS, "Cosine"},
{"tan", myprog_tan, METH_VARARGS, "Tangent"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef myprogmodule = {
PyModuleDef_HEAD_INIT, "myprog", NULL, -1, MyprogMethods
};
PyMODINIT_FUNC PyInit_myprog(void) {
return PyModule_Create(&myprogmodule);
}
myprog.c
というファイル名で上記のプログラムを保存し、以下のコマンドを実行します。
$ gcc -fpic -I/usr/include/python3.5 -o myprog.o -c myprog.c
$ gcc -shared myprog.o -o myprog.so
すると、myprog.so
というファイルが生成されます。
あとは、Pythonインタラクティブシェルを起動し、実際にC言語の関数を呼び出してみます。
$ python3
>>> import myprog
>>> myprog.sin(0.5)
0.479425538604203
>>> myprog.cos(0.5)
0.8775825618903728
>>> myprog.tan(0.5)
0.5463024898437905
では、Pythonでテストシナリオを書いてみましょう。
import myprog
import math
epsilon = 1
errors = 0
while epsilon > 0 and errors < 2:
theta = 0.0
while theta < 2 * math.pi:
diff = myprog.sin(theta) / myprog.cos(theta) - myprog.tan(theta)
if abs(diff) > epsilon:
print(epsilon, theta, diff)
errors = errors + 1
theta = theta + 0.1
epsilon = epsilon / 10
このプログラムの実行結果は以下のようになります。
1.0000000000000002e-14 4.699999999999999 1.4210854715202004e-14
1e-15 1.5000000000000002 1.7763568394002505e-15
1e-15 4.699999999999999 1.4210854715202004e-14
Lua版の実行結果と一部実行結果が異なりますが、一応それっぽいものが出力されました。
結果が異なるのは、浮動小数の変換精度の問題でしょうか。
PythonからC言語の関数を呼び出す方法についての詳細は、Pythonのドキュメントを参照してください。
まとめ
C言語で書かれたプログラムのデバッグにPythonスクリプトを用いる方法を紹介しました。
本で紹介されていたLuaスクリプトと違い、Pythonはメジャーでかつ多くのLinuxに標準で搭載されているので、デバッグ目的としては最適なスクリプト言語です。
ぜひ効率的なデバッギングを実現してください。