[インデックス 17135] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
に、go tool cover
コマンドが生成するカバレッジレポートのmode=count
形式のサポートを追加するものです。これにより、Emacs内でGoコードのカバレッジ強度を視覚的に表現できるようになります。具体的には、コードの実行回数に応じて異なる色を適用し、カバレッジの深さを一目で把握できるように改善されています。
コミット
commit 4a0d06c4c5f457e650ab816b76d4754e7f6cd34c
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Fri Aug 9 14:42:43 2013 -0700
misc/emacs: add support for mode=count coverage
Use the same algorithm that go tool cover uses when producing HTML
output to render coverage intensity.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12712043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4a0d06c4c5f457e650ab816b76d4754e7f6cd34c
元コミット内容
misc/emacs/go-mode.el
ファイルが変更され、78行が追加され、19行が削除されました。主な変更点は以下の通りです。
- カバレッジ強度を示すための新しいEmacsフェイス(
go-coverage-0
からgo-coverage-10
)が定義されました。 go--covered
構造体にcount
フィールドが追加され、コードブロックの実行回数を保持できるようになりました。go-coverage
関数が、mode=count
形式のカバレッジデータを解析し、実行回数に基づいてコードに色付きのオーバーレイを適用するように修正されました。go tool cover
のHTML出力が使用するのと同じ対数スケールアルゴリズムを適用し、カバレッジ強度を視覚的に表現します。- オーバーレイに
help-echo
プロパティが追加され、マウスカーソルを合わせたときに実行回数を表示するようになりました。
変更の背景
Go言語には、go test -coverprofile
コマンドによって生成されるコードカバレッジプロファイルと、それをHTMLレポートとして視覚化するgo tool cover
コマンドがあります。go tool cover
は、単にコードが実行されたかどうか(mode=set
)だけでなく、各コードブロックが何回実行されたか(mode=count
)という情報も提供できます。
このコミット以前のEmacsのgo-mode.el
は、おそらくmode=set
のカバレッジ情報(実行されたか否か)のみを視覚化する機能しか持っていなかったと考えられます。しかし、開発者にとっては、テストがコードのどの部分をどれだけ深くテストしているかを理解するために、実行回数に基づくカバレッジ強度(mode=count
)の情報が非常に有用です。
この変更の背景には、Emacsユーザーがgo tool cover
が提供する詳細なカバレッジ情報を、Emacsの統合開発環境内で直接利用できるようにするというニーズがありました。これにより、開発者はテストの網羅性をより詳細に分析し、テストが不足している領域や、特定のコードパスが頻繁に実行されている領域を特定しやすくなります。
前提知識の解説
Go言語のテストカバレッジ
Go言語の標準ツールチェーンには、コードカバレッジを測定する機能が組み込まれています。
go test -coverprofile=coverage.out
: このコマンドは、テスト実行中にどのコードが実行されたかの情報を収集し、coverage.out
というファイルにプロファイルとして出力します。go tool cover
: このコマンドは、coverage.out
ファイルの内容を解析し、様々な形式でカバレッジレポートを生成します。go tool cover -html=coverage.out
: 最も一般的に使用される形式で、カバレッジ情報を色分けされたHTMLページとして出力します。このHTMLレポートでは、実行されたコードは緑色、実行されなかったコードは赤色で表示されます。- カバレッジモード:
go test
は、カバレッジ情報を収集する際にいくつかのモードをサポートしています。mode=set
: 各ステートメントが実行されたかどうか(真偽値)のみを記録します。これはデフォルトのモードです。mode=count
: 各ステートメントが何回実行されたかを記録します。このモードは、テストが特定のコードパスをどれだけ頻繁に通過するかを理解するのに役立ちます。mode=atomic
:mode=count
に似ていますが、並行処理環境での正確なカウントのためにアトミック操作を使用します。
Emacs Lisp (Elisp) の基本概念
EmacsはEmacs Lisp(Elisp)というLisp方言で拡張可能です。このコミットで使われているElispの主要な概念は以下の通りです。
defface
: Emacsの表示属性(色、フォント、太字など)のセットを定義するために使用されます。定義されたフェイスは、テキストの特定の領域に適用して視覚的なスタイルを変更できます。defvar
: グローバル変数を定義します。defstruct
: 構造体(他のプログラミング言語の構造体やクラスに似たデータ構造)を定義します。これにより、関連するデータをまとめて扱うことができます。defun
: 関数を定義します。interactive
: 関数を対話的に呼び出せるようにします(例:M-x function-name
)。with-temp-buffer
: 一時的なバッファを作成し、その中でコードを実行します。コードの実行後、一時バッファは自動的に削除されます。insert-file-contents
: 指定されたファイルの内容を現在のバッファに挿入します。re-search-forward
: 正規表現を使って前方検索を行います。string-to-number
: 文字列を数値に変換します。file-name-nondirectory
: ファイルパスからディレクトリ部分を除いたファイル名のみを抽出します。make-overlay
: テキストの特定の領域に「オーバーレイ」を作成します。オーバーレイは、テキスト自体を変更せずに、その表示方法(色、フォントなど)や動作(クリック時の挙動、ツールチップなど)を変更するために使用されます。overlay-put
: 作成したオーバーレイにプロパティ(例:face
、help-echo
)を設定します。let*
: 変数を順番に束縛するためのマクロです。前の束縛が後の束縛で利用できます。cond
: 複数の条件分岐を記述するための特殊形式です。log
: 自然対数を計算する関数です。floor
: 数値を最も近い小さい整数に切り捨てる関数です。concat
: 複数の文字列を結合する関数です。number-to-string
: 数値を文字列に変換する関数です。format
: フォーマット文字列と引数を使って新しい文字列を生成します。
対数スケールによるカバレッジ強度の表現
go tool cover
のHTML出力では、コードの実行回数が多いほど色が濃くなるようなグラデーションが使われます。この際、実行回数をそのまま線形に色にマッピングすると、非常に大きな実行回数を持つブロックが少数あるだけで、他の多くのブロックの色がほとんど変わらなくなってしまいます。
これを解決するために、対数スケールが用いられます。対数スケールは、大きな数値の範囲をより小さな、管理しやすい範囲に圧縮するのに役立ちます。例えば、実行回数が1回、10回、100回、1000回といったように桁が異なる場合でも、対数を取るとそれぞれ0, 1, 2, 3といった線形に近い値になり、色のグラデーションに均等にマッピングしやすくなります。
このコミットでは、go tool cover
がHTML出力で利用しているのと同じ対数スケールアルゴリズムをEmacs Lispで実装し、カバレッジ強度を視覚的に表現しています。
技術的詳細
このコミットの核となる技術的詳細は、go tool cover
のHTML出力がカバレッジ強度を視覚化するために使用するアルゴリズムをEmacs Lispで再現している点にあります。
-
カバレッジデータの解析:
go-coverage
関数は、go test -coverprofile
によって生成されたカバレッジプロファイルファイル(例:coverage.out
)を読み込みます。このファイルは、各コードブロックの開始/終了位置と、そのブロックの実行回数(count
)を含む行指向のフォーマットを持っています。file:start_line.start_column,end_line.end_column count
例:
main.go:10.5,12.10 3
(main.goの10行目5列から12行目10列のブロックが3回実行された) -
最大実行回数 (
max-count
) の特定: 解析中に、すべてのコードブロックの中で最も高い実行回数(max-count
)を特定します。これは、カバレッジ強度の正規化の基準となります。 -
対数スケールによる正規化:
go tool cover
のアルゴリズムと同様に、実行回数count
を対数スケールで正規化します。divisor = (log max-count)
:max-count
の自然対数を計算し、これを正規化のための除数とします。norm = (/ (log count) divisor)
: 各コードブロックのcount
の自然対数をdivisor
で割ることで、count
を0から1の範囲(おおよそ)に正規化します。count=1
の場合、log(1)=0
なのでnorm
は0に近くなります。count=max-count
の場合、log(max-count)/log(max-count)=1
なのでnorm
は1になります。
-
色インデックスへのマッピング: 正規化された
norm
値は、0から9の範囲の整数n
にマッピングされます。n = (1+ (floor (* norm 9)))
(* norm 9)
:norm
を0から9の範囲にスケーリングします。floor
: 小数点以下を切り捨てて整数にします。1+
: 結果を1から10の範囲にシフトします。 このn
は、go-coverage-1
からgo-coverage-10
までのフェイス名の一部として使用されます。
-
特殊ケースのハンドリング:
count = 0
の場合:norm
は-0.1
に設定されます。これは、go-coverage-0
フェイス(通常は赤色で未カバーを示す)を適用するために使用されます。max-count = 1
の場合: これはmode=set
のカバレッジに相当します(実行されたか否かのみ)。この場合、norm
は0.8
に設定されます。これにより、n
は1 + floor(0.8 * 9) = 1 + floor(7.2) = 1 + 7 = 8
となり、go-coverage-8
フェイスが適用されます。go-coverage-8
は、以前のgo-coverage-covered
と同じ色(緑色)に設定されており、後方互換性を保ちつつ、mode=set
のカバレッジも適切に表示できるようにしています。
-
Emacsオーバーレイの適用: 計算された
n
に基づいて、go-coverage-N
という名前のフェイスが動的に構築され、対応するコードブロックにオーバーレイとして適用されます。これにより、コードの実行回数に応じた色のグラデーションがEmacsバッファに表示されます。 さらに、overlay-put
を使ってhelp-echo
プロパティが設定されます。これにより、ユーザーが色付けされたコードにマウスカーソルを合わせると、「Count: X」(Xは実際の実行回数)というツールチップが表示され、詳細な情報を提供します。
この一連の処理により、go tool cover
のHTMLレポートと同様の視覚的なカバレッジ強度表現がEmacs内で実現されます。
コアとなるコードの変更箇所
変更はすべて misc/emacs/go-mode.el
ファイル内で行われています。
-
新しいフェイスの定義:
(defface go-coverage-0 '((t (:foreground "#c00000"))) "Coverage color for uncovered code." :group 'go-cover) ;; ... go-coverage-1 to go-coverage-10 ... (defface go-coverage-10 '((t (:foreground "#14ec9b"))) "Coverage color for covered code with weight 10." :group 'go-cover)
既存の
go-coverage-uncovered
は削除され、go-coverage-0
がその役割を引き継ぎます。 -
go--covered
構造体の変更:--- a/misc/emacs/go-mode.el +++ b/misc/emacs/go-mode.el @@ -960,7 +999,7 @@ description at POINT." (point)))) (defstruct go--covered - start-line start-column end-line end-column covered) + start-line start-column end-line end-column covered count)
count
フィールドが追加されました。 -
go-coverage
関数の変更:- 新しい変数
max-count
とdivisor
の導入。 - カバレッジプロファイルの解析中に
max-count
を計算するロジックの追加。 make-go--covered
呼び出しでcount
フィールドをセット。- オーバーレイを適用するメインのループ内で、
count
、max-count
、divisor
を使ってnorm
とn
を計算し、動的にフェイス名を生成するロジックの追加。 overlay-put
でhelp-echo
プロパティを設定。
--- a/misc/emacs/go-mode.el +++ b/misc/emacs/go-mode.el @@ -968,7 +1007,9 @@ coverage information gathered via go test -coverprofile=INPUT." (interactive "fCoverage file: ") (let ((ranges '()) (file-name (file-name-nondirectory (buffer-file-name))) - (gocov-buffer-name (concat (buffer-name) "<gocov>")))) + (gocov-buffer-name (concat (buffer-name) "<gocov>")) + (max-count 0) + divisor)) (with-temp-buffer (insert-file-contents input) @@ -982,6 +1023,9 @@ coverage information gathered via go test -coverprofile=INPUT." (start-line start-column end-line end-column num count) (mapcar 'string-to-number rest) + (if (> count max-count) + (setq max-count count)) + (if (and (string= (file-name-nondirectory file) file-name)) (push (make-go--covered @@ -989,10 +1033,14 @@ coverage information gathered via go test -coverprofile=INPUT." :start-column start-column :end-line end-line :end-column end-column - :covered (/= count 0))\n+ :covered (/= count 0) + :count count)\n ranges)))) - (forward-line)))) + (forward-line)))) + + (if (> max-count 0) + (setq divisor (log max-count)))) (with-current-buffer (or (get-buffer gocov-buffer-name) @@ -1006,15 +1054,26 @@ coverage information gathered via go test -coverprofile=INPUT." 'face 'go-coverage-untracked) (dolist (range ranges) - (overlay-put - (make-overlay - (go--line-column-to-point - (go--covered-start-line range) - (go--covered-start-column range))\n- (go--line-column-to-point - (go--covered-end-line range) - (go--covered-end-column range))) - 'face (if (go--covered-covered range) 'go-coverage-covered 'go-coverage-uncovered)))) + (let* ((count (go--covered-count range)) + (norm (cond + ((= count 0) + -0.1) + ((= max-count 1) + 0.8) + (t + (/ (log count) divisor)))) + (n (1+ (floor (* norm 9)))) + (face (concat "go-coverage-" (number-to-string n))) + (ov (make-overlay + (go--line-column-to-point + (go--covered-start-line range) + (go--covered-start-column range)) + (go--line-column-to-point + (go--covered-end-line range) + (go--covered-end-column range)))))\n+ + (overlay-put ov 'face face) + (overlay-put ov 'help-echo (format "Count: %d" count)))))\n (display-buffer (current-buffer) 'display-buffer-reuse-window))))
- 新しい変数
コアとなるコードの解説
このコミットの最も重要な部分は、go-coverage
関数内の dolist
ループで、各カバレッジ範囲に対してオーバーレイを適用するロジックです。
(dolist (range ranges)
(let* ((count (go--covered-count range))
(norm (cond
((= count 0)
-0.1)
((= max-count 1)
0.8)
(t
(/ (log count) divisor))))
(n (1+ (floor (* norm 9))))
(face (concat "go-coverage-" (number-to-string n)))
(ov (make-overlay
(go--line-column-to-point
(go--covered-start-line range)
(go--covered-start-column range))
(go--line-column-to-point
(go--covered-end-line range)
(go--covered-end-column range)))))\n+
(overlay-put ov 'face face)
(overlay-put ov 'help-echo (format "Count: %d" count)))))\n
-
count
の取得:count (go--covered-count range)
: 現在処理しているカバレッジ範囲 (range
) から、そのコードブロックの実行回数 (count
) を取得します。 -
norm
の計算:norm
は、count
を正規化し、0から1の範囲に収めるための値です。((= count 0) -0.1)
: もしcount
が0(未実行)であれば、norm
は-0.1
に設定されます。これは後でgo-coverage-0
フェイスにマッピングされます。((= max-count 1) 0.8)
: もしmax-count
が1であれば、これはmode=set
のカバレッジ(実行されたか否かのみ)を意味します。この場合、norm
は0.8
に設定されます。これにより、n
は8
になり、go-coverage-8
フェイスが適用されます。このフェイスは、以前の「カバーされたコード」の色と同じです。(t (/ (log count) divisor))
: 上記のどちらでもない場合(つまり、count > 0
かつmax-count > 1
)、count
の自然対数をdivisor
で割ることでnorm
を計算します。divisor
は(log max-count)
であり、これによりnorm
はlog_max_count(count)
となり、0から1の範囲に正規化されます。
-
n
の計算:n (1+ (floor (* norm 9)))
:norm
を0から9の範囲にスケーリングし、小数点以下を切り捨て、さらに1を加えることで、1から10の整数値n
を生成します。このn
が、どのgo-coverage-N
フェイスを使用するかを決定します。 -
face
名の生成:face (concat "go-coverage-" (number-to-string n))
:n
の値を使って、"go-coverage-0"
、"go-coverage-1"
、...、"go-coverage-10"
といったフェイス名を動的に生成します。 -
オーバーレイの作成とプロパティの設定:
ov (make-overlay ...)
: 現在のカバレッジ範囲に対応するテキスト領域に新しいオーバーレイを作成します。go--line-column-to-point
は、行と列の情報をEmacsバッファ内のポイント(文字位置)に変換するヘルパー関数です。(overlay-put ov 'face face)
: 生成されたフェイス名 (face
) をオーバーレイに適用し、コードの色を変更します。(overlay-put ov 'help-echo (format "Count: %d" count))
: オーバーレイにhelp-echo
プロパティを設定します。これにより、ユーザーがこのオーバーレイされたコードにマウスカーソルを合わせると、「Count: X」(Xは実際の実行回数)というツールチップが表示されます。
このロジックにより、go tool cover
のHTML出力と同様に、コードの実行回数に応じたグラデーションカラーと詳細な実行回数情報がEmacs上で視覚的に提供されるようになります。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
go test
コマンドのドキュメント: https://go.dev/cmd/go/#hdr-Test_packagesgo tool cover
コマンドのドキュメント: https://go.dev/cmd/go/#hdr-Go_tool_cover- Emacs Lisp リファレンスマニュアル: https://www.gnu.org/software/emacs/manual/html_node/elisp/
参考にした情報源リンク
- Go言語の
cmd/cover/html.go
ソースコード (カバレッジHTMLレポートの生成ロジック):- https://github.com/golang/go/blob/master/src/cmd/cover/html.go
- 特に
color
関数やpercentToColor
関数が、色のグラデーションと対数スケールマッピングに関連するロジックを含んでいます。
- Emacs Lisp の
defface
ドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Faces.html - Emacs Lisp の
defstruct
ドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Structures.html - Emacs Lisp の
overlay-put
ドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Overlay-Properties.html - Emacs Lisp の
log
関数ドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Math-Functions.html