👌

printfの再実装をしたたかにやってみる(5)

に公開

まえおき

これはシリーズ記事です。
https://zenn.dev/monksoffunk/articles/c311aab03d6dfc
https://zenn.dev/monksoffunk/articles/664f5407027e81
https://zenn.dev/monksoffunk/articles/495a5096a5bc42
https://zenn.dev/monksoffunk/articles/0bed0c1c94807d

掲載するコードは説明のために書かれたものであり動作の保証はありません。また、完成形のコードは公開しません。ぜひご自身で書いてください。

テストを書く

みなさんコードを書いたらテストコードも並行して書いていることと思います。書いたコードが期待したふるまいをするかどうかを確認せずに開発を進めることは危険です。実は、コードを書く人間とテストを書く人間が同じであることのデメリットは少なくありません。なぜなら、テストとは思いの至らないケースも盛り込まれるべきであるのに対して、コードを書いた人間が思いつくテストケースはコードが想定した範囲内にとどまりがちだからです。

テストを書くのは意地悪な人が向いています。自分で書いたコードのテストであっても、別人格になったつもりでとことん意地悪なテストケースを考えるようにします。

再実装系のプロジェクトは、期待する挙動として本家の出力を比較対象にできるため検証が簡単です。
同じ入力を渡して同じ出力が返ればOK。違ったらNGです。
printfで言えば出力と返り値の2つを見ることになります。

printfの本家と自作の2つの関数の出力をシンプルに比較するにはシェルスクリプトを使うなどして(あるいはCを含め他の言語でもいいですが)標準出力を受け取る方法がありますが、今回はvsnprintfを作ってバッファで出力文字列を受け取るようにC言語でテストを書きました。以下に比較関数を示します。

void cmp_snprintf(size_t size, const char *format, ...)
{
	total_tests++;
	va_list args1, args2;
	va_start(args1, format);
	va_start(args2, format);

	char *my_buf = malloc(size);
	char *libc_buf = malloc(size);
	if (!my_buf || !libc_buf)
	{
		printf("malloc failed\n");
		exit(1);
	}

	// Initialize buffers to a known state to detect unterminated strings.
	memset(my_buf, 'Z', size);
	memset(libc_buf, 'Z', size);

	int my_ret = my_vsnprintf(my_buf, size, format, args1);
	int libc_ret = vsnprintf(libc_buf, size, format, args2);

	va_end(args1);
	va_end(args2);

	int ok = (my_ret == libc_ret) && (strcmp(my_buf, libc_buf) == 0);
	if (ok)
		ok_tests++;
	print_test_result(ok, format, my_buf, my_ret, libc_buf, libc_ret, total_tests);
	free(my_buf);
	free(libc_buf);
}

print_test_result()では出力結果を表示するように適宜コードを書きます。

void print_test_result(int ok, const char *format, const char *my_buf, int my_ret, const char *libc_buf, int libc_ret, int test_no)
{
    printf("--- TEST %d ---\n", test_no);
    printf("FORMAT: %s\n", format);
    printf("my  : %s :%d\n", my_buf, my_ret);
    printf("libc: %s :%d\n", libc_buf, libc_ret);
    if (ok)
        printf("\x1b[32m[OK]\x1b[0m\n");
    else
        printf("\x1b[31m[KO]\x1b[0m\n");
    printf("-------------------------------------\n");
}

例として%dのテストケースの一部を以下に貼ります。

	cmp_snprintf(2048, "[%d]", 42);
	cmp_snprintf(2048, "[%d]", -42);
	cmp_snprintf(2048, "[%+d]", 42);
	cmp_snprintf(2048, "[%04d]", 42);
	cmp_snprintf(2048, "[%.4d]", 42);
	cmp_snprintf(2048, "[%.2d]", 42);
	cmp_snprintf(2048, "[%.1d]", 42);
	cmp_snprintf(2048, "[%0.1d]", 42);
	cmp_snprintf(2048, "[%+.1d]", 42);
	cmp_snprintf(2048, "[%.4d]", -42);
	cmp_snprintf(2048, "[%.2d]", -42);
	cmp_snprintf(2048, "[%.1d]", -42);
	cmp_snprintf(2048, "[%0.1d]", -42);
	cmp_snprintf(2048, "[%+.1d]", -42);
	cmp_snprintf(2048, "[%20.10d]", 42);
	cmp_snprintf(2048, "[%+20.10d]", -42);
	cmp_snprintf(2048, "%d", INT_MAX);
	cmp_snprintf(2048, "%d", INT_MIN);
	cmp_snprintf(2048, "%d", 0);
	cmp_snprintf(2048, " %-12d ", LONG_MIN);
	cmp_snprintf(2048, " %-13d ", UINT_MAX);
	cmp_snprintf(2048, " %-14d ", ULONG_MAX);
	cmp_snprintf(2048, " %-15d ", 923372036854775807LL);
	cmp_snprintf(2048, " %-9d %-10d %-11d %-12d %-13d %-14d %-15d", INT_MAX, INT_MIN, LONG_MAX, LONG_MIN, ULONG_MAX, 0, -42);
	cmp_snprintf(2048, " %02d ", -1);
	cmp_snprintf(2048, " %04d ", -14);
	cmp_snprintf(2048, " %05d ", -15);
	cmp_snprintf(2048, " %06d ", -16);
	cmp_snprintf(2048, " %01d ", -99);
	cmp_snprintf(2048, " %02d ", -100);
	cmp_snprintf(2048, " %03d ", -101);
	cmp_snprintf(2048, " %09d ", INT_MAX);
	cmp_snprintf(2048, " %010d ", INT_MIN);
	cmp_snprintf(2048, " %011d ", LONG_MAX);
	cmp_snprintf(2048, " %012d ", LONG_MIN);
	cmp_snprintf(2048, " %013d ", UINT_MAX);
	cmp_snprintf(2048, " %014d ", ULONG_MAX);
	cmp_snprintf(2048, " %015d ", 923372036854775807LL);
	cmp_snprintf(2048, " %09d %010d %011d %012d %013d %014d %015d", INT_MAX, INT_MIN, LONG_MAX, LONG_MIN, ULONG_MAX, 0, -42);
	cmp_snprintf(2048, " %.1d ", 0);
	cmp_snprintf(2048, " %.3d ", -11);
	cmp_snprintf(2048, " %.4d ", -14);
	cmp_snprintf(2048, " %.1d ", -15);
	cmp_snprintf(2048, " %.2d ", -16);
	cmp_snprintf(2048, " %.3d ", -99);
	cmp_snprintf(2048, " %.3d ", -100);
	cmp_snprintf(2048, " %.4d ", -101);
	cmp_snprintf(2048, " %.8d ", INT_MAX);
	cmp_snprintf(2048, " %.9d ", INT_MIN);
	cmp_snprintf(2048, " %.10d ", LONG_MAX);
	cmp_snprintf(2048, " %.11d ", LONG_MIN);
	cmp_snprintf(2048, " %.12d ", UINT_MAX);
	cmp_snprintf(2048, " %.13d ", ULONG_MAX);
	cmp_snprintf(2048, " %.14d ", 923372036854775807LL);
	cmp_snprintf(2048, " %.8d %.9d %.10d %.11d %.12d %.13d %.14d", INT_MAX, INT_MIN, LONG_MAX, LONG_MIN, ULONG_MAX, 0, -42);
	cmp_snprintf(2048, " % d ", 0);
	cmp_snprintf(2048, " % d ", -1);
	cmp_snprintf(2048, " % d ", 1);
	cmp_snprintf(2048, " % d ", 9);
	cmp_snprintf(2048, " % d ", 10);
	cmp_snprintf(2048, " % d ", 101);
	cmp_snprintf(2048, " % d ", -9);
	cmp_snprintf(2048, " % d ", -10);
	cmp_snprintf(2048, " % d ", -101);
	cmp_snprintf(2048, " % d ", INT_MAX);
	cmp_snprintf(2048, " % d ", INT_MIN);
	cmp_snprintf(2048, " % d ", LONG_MAX);
	cmp_snprintf(2048, " % d ", LONG_MIN);
	cmp_snprintf(2048, " % d ", UINT_MAX);
	cmp_snprintf(2048, " % d ", ULONG_MAX);
	cmp_snprintf(2048, " % d ", 9223372036854775807LL);
	cmp_snprintf(2048, " % d % d % d % d % d % d % d", INT_MAX, INT_MIN, LONG_MAX, LONG_MIN, ULONG_MAX, 0, -42);
	cmp_snprintf(2048, " %.0d ", 0);
	cmp_snprintf(2048, " %.0d ", 42);
	cmp_snprintf(2048, " %2.0d ", 0);
	cmp_snprintf(2048, " % .2d ", 0);
	cmp_snprintf(2048, " % .3d ", 0);
	cmp_snprintf(2048, " % .11d ", 2147483647);
	cmp_snprintf(2048, " %.0d ", 0);
	cmp_snprintf(2048, " %.1d ", 0);
	cmp_snprintf(2048, " %.2d ", 0);
	cmp_snprintf(2048, " %.3d ", 0);
	cmp_snprintf(2048, " %3.1d ", -1);
	cmp_snprintf(2048, " %03d ", -1);
	cmp_snprintf(2048, " %0.3d ", -1);
	cmp_snprintf(2048, " %03d ", 1);
	cmp_snprintf(2048, " %0.3d ", 1);
	cmp_snprintf(2048, " %+03d ", 1);
	cmp_snprintf(2048, " %+0.3d ", 1);
	cmp_snprintf(2048, "%ld", LONG_MAX);
	cmp_snprintf(2048, "%ld", LONG_MIN);
	cmp_snprintf(2048, "%lld", LLONG_MAX);
	cmp_snprintf(2048, "%lld", LLONG_MIN);
	cmp_snprintf(2048, "%10ld", 42);
	cmp_snprintf(2048, "%10lld", 42);
	cmp_snprintf(2048, "%zd", (ssize_t)-1);
	cmp_snprintf(2048, "%zd", (ssize_t)-100000);
	cmp_snprintf(2048, "%lld", (long long)-100000);

こうしたテストケースをどんどん作って挙動を確認しつつ実装を進めることになります。

Discussion