[インデックス 1002] errchkスクリプトのBUGメッセージ重複出力修正
コミット
- コミットハッシュ:
eb5a316fa6e5a12d10f7054ff3d9de608d772278
- 作成者: Russ Cox rsc@golang.org
- 作成日: 2008年10月30日 12:43:32 (PDT)
- メッセージ: make sure errchk only prints BUG once. using a variable is not sufficient, because sometimes bug() is called from a subshell.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eb5a316fa6e5a12d10f7054ff3d9de608d772278
元コミット内容
commit eb5a316fa6e5a12d10f7054ff3d9de608d772278
Author: Russ Cox <rsc@golang.org>
Date: Thu Oct 30 12:43:32 2008 -0700
make sure errchk only prints BUG once.
using a variable is not sufficient, because
sometimes bug() is called from a subshell.
R=iant
DELTA=7 (2 added, 1 deleted, 4 changed)
OCL=18092
CL=18145
---
test/errchk | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/test/errchk b/test/errchk
index a8476a6258..2b602c3c7f 100755
--- a/test/errchk
+++ b/test/errchk
@@ -29,9 +29,11 @@ TMPERR=/tmp/errchk-err-$$
TMPALL=/tmp/errchk-all-$$
TMPTMP=/tmp/errchk-tmp-$$
TMPSTAT=/tmp/errchk-stat-$$
-rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT
+TMPBUG=/tmp/errchk-bug-$$
-trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT" 0 1 2 3 14 15
+rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
+
+trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
if $* >$TMPOUT 2>$TMPERR; then
echo 1>&2 "BUG: errchk: command succeeded unexpectedly: " "$@"
@@ -43,12 +45,11 @@ fi
cat $TMPOUT $TMPERR | grep -v '^ ' > $TMPALL
-didbug=false
bug() {
- if ! $didbug
+ if ! test -f $TMPBUG
then
echo 1>&2 -n BUG: ''
- didbug=true
+ echo >$TMPBUG
fi
}
変更の背景
このコミットは、Goコンパイラの初期開発段階(2008年)において、テストツールerrchk
の重要なバグ修正を行っています。Go言語プロジェクトは2007年9月21日にRobert Griesemer、Rob Pike、Ken Thompsonによって開始され、2008年1月にはKen Thompsonが最初のコンパイラ開発を開始していました。
当時のGoコンパイラはまだ実験的な段階にあり、C言語への変換を行うプロトタイプコンパイラとして動作していました。この時期には、コンパイラの動作確認やエラーメッセージの検証のために、様々なテストツールが必要でした。errchk
はその中でも重要な役割を果たしており、コンパイラが出力するエラーメッセージが期待通りであることを確認するために使用されていました。
問題の発生背景:
errchk
ツールは、Goコンパイラのエラーメッセージを検証するためのシェルスクリプトでした- このツールは、テストケースで発生するエラーが期待されるパターンと一致するかを確認する重要な役割を担っていました
- しかし、複数回の"BUG"メッセージが出力される問題が発生していました
- 特に、サブシェルから
bug()
関数が呼び出される際に、変数による重複防止機能が正常に動作しない問題がありました
前提知識の解説
errchkツールについて
errchk
は、Goコンパイラの初期開発において使用されていたシェルスクリプトベースのテストツールです。このツールは以下の機能を提供していました:
- エラーメッセージの検証: コンパイラが出力するエラーメッセージが期待されるパターンと一致するかをチェック
- テストの自動化: コンパイラテストの実行とエラー報告を自動化
- 回帰テストの実行: 新しい変更がコンパイラの動作に悪影響を与えていないかを検証
シェルスクリプトでの変数スコープ問題
シェルスクリプトにおいて、サブシェルで実行される関数は親シェルの変数を変更できません。これは、サブシェルが独立したプロセス空間で動作するためです。例えば:
flag=false
(
flag=true # サブシェル内での変更
)
echo $flag # 依然として false
この特性により、didbug
変数を使用したBUGメッセージの重複防止機能が、サブシェルからbug()
関数が呼び出される場合に正しく動作しませんでした。
一時ファイルによる状態管理
シェルスクリプトで複数のプロセス間で状態を共有する際の一般的な解決策として、一時ファイルを使用する方法があります:
- プロセス間通信: 複数のプロセスが同じファイルシステムを参照することで状態を共有
- 原子操作: ファイルの作成・削除は原子的操作として扱われる
- 持続性: プロセスが終了しても状態が保持される
2008年のGo開発環境
このコミットが作成された2008年は、Go言語の開発初期段階でした:
- Goはまだ公開されておらず、Googleの内部プロジェクトとして開発中
- Robert Griesemer、Rob Pike、Ken Thompsonらが中心となって言語設計を進めていた
- Russ Coxは、コンパイラとランタイムシステムの実装を担当していた
- テストインフラストラクチャも同時に構築されていた時期
- 最初のGoプログラムが2008年2月6日に動作し、Ken Thompsonが最初のコンパイラを開発していた
技術的詳細
修正前の実装(変数ベース)
didbug=false
bug() {
if ! $didbug
then
echo 1>&2 -n BUG: ''
didbug=true
fi
}
この実装では、didbug
変数を使用してBUGメッセージが既に出力されたかどうかを記録していました。しかし、bug()
関数がサブシェルから呼び出された場合、以下の問題が発生しました:
- スコープの分離: サブシェルでの
didbug=true
は親シェルの変数に影響しない - 状態の非同期: 複数のサブシェルが同時に実行される場合、それぞれが独立した
didbug
変数を持つ - 重複出力: 各サブシェルで最初の
bug()
呼び出し時にBUGメッセージが出力される
修正後の実装(ファイルベース)
TMPBUG=/tmp/errchk-bug-$$
rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
bug() {
if ! test -f $TMPBUG
then
echo 1>&2 -n BUG: ''
echo >$TMPBUG
fi
}
この修正では、一時ファイル$TMPBUG
を使用してBUGメッセージの出力状態を管理しています:
- プロセス間での状態共有: ファイルシステムを通じて全てのプロセスが同じ状態を参照
- 原子的な状態確認:
test -f
コマンドによるファイル存在チェック - 適切なクリーンアップ: trapによる一時ファイルの確実な削除
問題の具体的な発生パターン
# 例:サブシェルでの実行パターン
(
# サブシェル内で実行
some_command_that_triggers_bug_function
bug "エラーメッセージ"
)
この場合、親シェルで設定された「既に"BUG"を出力した」というフラグ変数がサブシェルに継承されないため、重複出力が発生していました。
コアとなるコードの変更箇所
1. 一時ファイル変数の追加
TMPSTAT=/tmp/errchk-stat-$$
-rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT
+TMPBUG=/tmp/errchk-bug-$$
-trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT" 0 1 2 3 14 15
+rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
+
+trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
2. bug()関数の修正
-didbug=false
bug() {
- if ! $didbug
+ if ! test -f $TMPBUG
then
echo 1>&2 -n BUG: ''
- didbug=true
+ echo >$TMPBUG
fi
}
コアとなるコードの解説
一時ファイル名の生成
TMPBUG=/tmp/errchk-bug-$$
$$
は現在のプロセスID(PID)を表す特殊変数- 複数の
errchk
インスタンスが同時実行されても、PIDが異なるため一意なファイル名が生成される /tmp/errchk-bug-1234
のような形式になる
ファイル初期化とクリーンアップ
rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG
-f
オプションにより、ファイルが存在しない場合でもエラーにならない- スクリプト開始時に古い一時ファイルを確実に削除
trapによるクリーンアップ
trap "rm -f $TMPOUT $TMPERR $TMPALL $TMPTMP $TMPSTAT $TMPBUG" 0 1 2 3 14 15
trap
コマンドは、指定されたシグナルを受信した際にコマンドを実行0
: 正常終了時1
: SIGHUP(ハングアップ)2
: SIGINT(割り込み、通常Ctrl+C)3
: SIGQUIT(終了)14
: SIGALRM(アラーム)15
: SIGTERM(終了要求)
状態確認と更新
if ! test -f $TMPBUG
then
echo 1>&2 -n BUG: ''
echo >$TMPBUG
fi
test -f $TMPBUG
: ファイルが存在し、かつ通常ファイルかどうかをチェックecho >$TMPBUG
: 空のファイルを作成(既存の場合は空にする)echo 1>&2 -n BUG: ''
: 標準エラー出力に改行なしで"BUG: "を出力
修正の技術的意義
- プロセス間での状態共有: ファイルシステムを使用した状態管理により、サブシェルとの状態共有を実現
- 堅牢性の向上: 変数ベースの不安定な解決策から、より確実な方法への移行
- テストの信頼性向上: 重複出力の排除により、テスト結果の解析が容易になる
関連リンク
- Go Programming Language - Official Site
- Go First Program Blog Post
- Go Release History
- Go GitHub Repository
- Russ Cox Research Site
- Go Language Specification
参考にした情報源リンク
- Go Issue #25669 - test: remove Perl script errchk
- Go Issue #20007 - cmd/vet: tests require Perl for errchk
- Go Issue #4141 - test: do run.go's errorcheck and errchk really match?
- Go Issue #2833 - test/run: replace shell script with go program
- Go by Example - Temporary Files
- Go Documentary - Historical Context
- errcheck tool by kisielk
- testscript package documentation