[インデックス 17107] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
に、go test -coverprofile
コマンドによって生成されるコードカバレッジの出力をEmacsバッファ上に視覚的に表示する機能を追加するものです。具体的には、カバレッジ情報をオーバーレイとしてレンダリングし、go tool cover
が生成するHTMLレポートの見た目をEmacs内で再現します。また、いくつかのコードの整理も含まれています。
コミット
commit 6e5f4bab90456e845d2e4a436718d4ab4cfee238
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Thu Aug 8 15:54:26 2013 -0700
misc/emacs: Add support for code coverage output of go test
Renders code coverage as an overlay, replicating the look of the
HTML that go tool cover produces.
Also some cleanups.
R=adonovan, bradfitz
CC=golang-dev
https://golang.org/cl/12684043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6e5f4bab90456e845d2e4a436718d4ab4cfee238
元コミット内容
misc/emacs: Add support for code coverage output of go test
Renders code coverage as an overlay, replicating the look of the
HTML that go tool cover produces.
Also some cleanups.
R=adonovan, bradfitz
CC=golang-dev
https://golang.org/cl/12684043
変更の背景
Go言語には、テスト実行時にコードカバレッジ情報を収集する機能が標準で備わっています。go test -coverprofile=coverage.out
コマンドを実行すると、coverage.out
というファイルにカバレッジデータがテキスト形式で出力されます。このデータは、どのコード行がテストによって実行されたか(カバーされたか)、あるいは実行されなかったか(カバーされなかったか)を示します。
通常、このcoverage.out
ファイルはgo tool cover -html=coverage.out
コマンドを使ってHTML形式のレポートに変換され、ウェブブラウザで視覚的に確認されます。このHTMLレポートでは、カバーされたコードは緑色、カバーされなかったコードは赤色でハイライト表示され、コードのテスト網羅率を一目で把握できるようになっています。
このコミットの背景には、EmacsユーザーがGoコードを編集する際に、IDEやブラウザに切り替えることなく、Emacs内で直接コードカバレッジ情報を視覚的に確認したいというニーズがありました。これにより、開発者はテスト結果とコードの関連性をより迅速に把握し、テストの不足している部分を特定しやすくなります。
前提知識の解説
Go言語のコードカバレッジ
Go言語のコードカバレッジは、プログラムのテスト中に実行されたコードの割合を測定する機能です。
go test -coverprofile=file.out
: このコマンドは、テストを実行し、その結果として生成されるカバレッジデータを指定されたファイル(例:file.out
)に保存します。このファイルは、各ソースファイルのどの部分が実行されたか、または実行されなかったかを示すテキスト形式のデータを含みます。go tool cover
:go test -coverprofile
で生成されたカバレッジファイルを解析し、人間が読みやすい形式(通常はHTML)に変換するためのツールです。go tool cover -html=file.out
: カバレッジデータをHTMLレポートとして生成し、ウェブブラウザで開きます。このレポートでは、カバーされたコードは緑色、カバーされなかったコードは赤色で表示されます。
Emacs Lisp (Elisp)
Emacs Lispは、Emacsエディタの拡張言語です。Emacsのほぼ全ての機能はEmacs Lispで書かれており、ユーザーはEmacs Lispを使ってEmacsをカスタマイズしたり、新しい機能を追加したりできます。
defgroup
: 関連するカスタマイズオプションをグループ化するために使用されます。これにより、M-x customize-group
コマンドなどでオプションを整理して表示できます。defcustom
: ユーザーがカスタマイズ可能な変数を定義するために使用されます。これにより、ユーザーはEmacsの設定ファイル(.emacs
やinit.el
)で変数の値を変更できます。defface
: Emacsのテキスト表示における「フェイス」(フォント、色、背景色などの属性の集合)を定義するために使用されます。これにより、特定のテキストに視覚的なスタイルを適用できます。overlay
: Emacsの強力な機能の一つで、バッファのテキストの上に視覚的な属性(色、フォント、ツールチップなど)を重ねて表示するために使用されます。オーバーレイは実際のバッファテキストを変更しないため、一時的なハイライトや情報表示に適しています。clone-indirect-buffer
: 既存のバッファの「間接的なクローン」を作成する関数です。間接的なバッファは、元のバッファと同じテキスト内容を共有しますが、独自のポイント、マーク、オーバーレイ、モードラインなどを持ちます。これにより、同じファイルの内容を異なる表示設定で同時に見ることができます。
技術的詳細
このコミットの主要な技術的詳細は、go-mode.el
にgo test -coverprofile
の出力を解析し、Emacsのオーバーレイ機能を使って視覚化するロジックを追加した点にあります。
-
カバレッジデータの読み込みと解析:
go-coverage
関数は、go test -coverprofile
によって生成されたカバレッジファイルのパスを引数として受け取ります。with-temp-buffer
を使って一時バッファにカバレッジファイルの内容を読み込みます。- カバレッジファイルは、各行が
ファイル名:開始行.開始列,終了行.終了列 実行回数
のような形式で記述されています。例えば、main.go:10.5,12.20 1 1
は、main.go
の10行目5列から12行目20列までのコードブロックが1回実行されたことを意味します。 split-string
やmapcar 'string-to-number
、destructuring-bind
といったEmacs Lispの関数を駆使して、このテキストデータを解析し、各カバレッジブロックのファイル名、開始/終了の行/列、そして実行回数(カバーされたかどうか)を抽出します。- 抽出されたデータは、新しく定義された
go--covered
構造体(defstruct
で定義)のインスタンスとしてリストに格納されます。この構造体は、カバレッジ範囲の開始/終了位置と、その範囲がカバーされたかどうかを示すブール値(covered
)を保持します。
-
Emacsオーバーレイによる視覚化:
go-coverage
関数は、カバレッジを表示したい元のGoソースコードのバッファの間接的なクローンを作成します(clone-indirect-buffer
)。これにより、元のバッファは変更されずに、カバレッジ情報が適用された新しいバッファが作成されます。- まず、作成されたクローンバッファ全体に
go-coverage-untracked
フェイス(灰色)のオーバーレイを適用します。これは、カバレッジデータに含まれていない(つまり、カバレッジツールによって追跡されていない)コード部分のデフォルトの表示となります。 - 次に、解析された各カバレッジブロック(
ranges
リスト内のgo--covered
インスタンス)についてループ処理を行います。 - 各カバレッジブロックの開始/終了の行/列情報を使って、
go--line-column-to-point
関数でEmacsバッファ内の対応する「ポイント」(文字位置)を計算します。 - 計算された開始ポイントと終了ポイントを使って新しいオーバーレイを作成します(
make-overlay
)。 - このオーバーレイには、カバレッジブロックがカバーされているかどうかに応じて、
go-coverage-covered
フェイス(緑色)またはgo-coverage-uncovered
フェイス(赤色)が適用されます。これにより、go tool cover
のHTMLレポートと同様の視覚効果がEmacs内で実現されます。 - 最終的に、カバレッジ情報が適用されたクローンバッファが
display-buffer
によって表示されます。
-
ユーティリティ関数の追加とリファクタリング:
go--goto-line
関数が新しく追加され、指定された行番号にカーソルを移動する処理をカプセル化しています。これにより、既存のコードベースで複数回出現していたgoto-char (point-min)
とforward-line (1- line)
の組み合わせが共通化され、コードの可読性と保守性が向上しています。go--line-column-to-point
関数は、行番号と列番号からEmacsバッファのポイント(文字位置)を計算するユーティリティ関数です。これは、カバレッジデータの解析結果をEmacsのオーバーレイ機能に適用するために不可欠です。
コアとなるコードの変更箇所
misc/emacs/go-mode.el
ファイルが変更されています。
主な変更点は以下の通りです。
-
フェイス定義の追加:
(defface go-coverage-untracked '((t (:foreground "#505050"))) "Coverage color of untracked code." :group 'go-cover) (defface go-coverage-covered '((t (:foreground "#2cd495"))) "Coverage color of covered code." :group 'go-cover) (defface go-coverage-uncovered '((t (:foreground "#c00000"))) "Coverage color of uncovered code." :group 'go-cover)
カバレッジ表示用の3つの新しいフェイスが定義されています。
-
ユーティリティ関数の追加:
(defun go--goto-line (line) (goto-char (point-min)) (forward-line (1- line))) (defun go--line-column-to-point (line column) (save-excursion (go--goto-line line) (forward-char (1- column)) (point)))
行番号への移動と、行・列からポイントへの変換を行うヘルパー関数が追加されています。
-
カバレッジデータ構造の定義:
(defstruct go--covered start-line start-column end-line end-column covered)
カバレッジ範囲の情報を保持するための構造体が定義されています。
-
go-coverage
関数の追加:(defun go-coverage (input) "Open a clone of the current buffer and overlay it with 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>")))) (with-temp-buffer (insert-file-contents input) (go--goto-line 2) ;; Skip over mode (while (not (eobp)) (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":")) (file (car parts)) (rest (split-string (nth 1 parts) "[., ]")))) (destructuring-bind (start-line start-column end-line end-column num count) (mapcar 'string-to-number rest) (if (and (string= (file-name-nondirectory file) file-name)) (push (make-go--covered :start-line start-line :start-column start-column :end-line end-line :end-column end-column :covered (/= count 0)) ranges)))) (forward-line)))) (with-current-buffer (or (get-buffer gocov-buffer-name) (clone-indirect-buffer gocov-buffer-name nil))) (save-excursion (overlay-put (make-overlay (point-min) (point-max)) '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)) (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)))) (display-buffer (current-buffer) 'display-buffer-reuse-window))))
この関数がカバレッジファイルを読み込み、解析し、オーバーレイを適用して表示する主要なロジックを含んでいます。
-
既存コードのリファクタリング:
go-play-buffer
、go-unused-imports-lines
、go-mode-find-file-at-point
などの既存の関数内で、行移動のロジックが新しく定義されたgo--goto-line
関数を使用するように変更されています。
コアとなるコードの解説
このコミットの核となるのは、go-mode.el
に追加されたgo-coverage
関数です。
-
go-coverage
関数のインタフェースと初期設定:(interactive "fCoverage file: ")
により、この関数がEmacsのM-xコマンドとして実行可能になり、ユーザーにカバレッジファイルのパスを入力するプロンプトが表示されます。let
フォーム内で、カバレッジ範囲を格納するリストranges
、現在のバッファのファイル名file-name
、カバレッジ表示用バッファの名前gocov-buffer-name
が初期化されます。
-
カバレッジファイルの読み込みと解析:
(with-temp-buffer (insert-file-contents input) ...)
: 指定されたカバレッジファイルの内容を一時的なバッファに読み込みます。(go--goto-line 2)
: カバレッジファイルの最初の行(通常はmode: set
のようなヘッダ)をスキップします。(while (not (eobp)) ...)
: ファイルの最後まで各行を処理します。(let* ((parts (split-string ...)) (file (car parts)) (rest (split-string ...))))
: 各行を:
で分割してファイル名とカバレッジ情報部分を取得し、さらにカバレッジ情報部分を,
や.
、スペースで分割して数値に変換します。(destructuring-bind (start-line start-column end-line end-column num count) (mapcar 'string-to-number rest) ...)
: 分割された文字列を数値に変換し、start-line
などの変数にバインドします。num
はブロック内のステートメント数、count
は実行回数です。(if (and (string= (file-name-nondirectory file) file-name)) ...)
: 読み込んだカバレッジデータが現在のEmacsバッファで開かれているファイルと一致する場合のみ処理を進めます。(push (make-go--covered ...) ranges)
: 抽出したカバレッジ情報をgo--covered
構造体のインスタンスとして作成し、ranges
リストの先頭に追加します。covered
フィールドは(/= count 0)
、つまり実行回数が0でなければt
(真)となります。
-
カバレッジ表示バッファの準備:
(with-current-buffer (or (get-buffer gocov-buffer-name) (clone-indirect-buffer gocov-buffer-name nil)) ...)
:- まず、既にカバレッジ表示用のバッファが存在するかどうかを
get-buffer
で確認します。 - 存在しない場合は、現在のGoソースコードバッファの「間接的なクローン」を
clone-indirect-buffer
で作成します。間接的なクローンは元のバッファと同じテキスト内容を共有しますが、独自のオーバーレイを持つことができます。 - このカバレッジ表示用バッファを現在のバッファとして設定します。
- まず、既にカバレッジ表示用のバッファが存在するかどうかを
-
オーバーレイの適用:
(save-excursion ...)
: オーバーレイ適用中にポイント(カーソル位置)が移動しても、処理後に元の位置に戻るようにします。(overlay-put (make-overlay (point-min) (point-max)) 'face 'go-coverage-untracked)
: まず、バッファ全体にgo-coverage-untracked
フェイス(灰色)のオーバーレイを適用します。これは、カバレッジデータで明示的にカバーされたりされなかったりしない部分のデフォルトの背景色となります。(dolist (range ranges) ...)
: 解析された各カバレッジ範囲(go--covered
インスタンス)についてループします。(make-overlay (go--line-column-to-point ...) (go--line-column-to-point ...))
:go--covered
構造体から取得した開始/終了の行/列情報を使って、go--line-column-to-point
関数でEmacsバッファ内の正確な文字位置(ポイント)を計算し、その範囲に新しいオーバーレイを作成します。(overlay-put ... 'face (if (go--covered-covered range) 'go-coverage-covered 'go-coverage-uncovered))
: カバレッジ範囲がカバーされているかどうかに応じて、go-coverage-covered
フェイス(緑色)またはgo-coverage-uncovered
フェイス(赤色)をオーバーレイに適用します。これにより、先に適用されたgo-coverage-untracked
のオーバーレイが上書きされ、正確なカバレッジ情報が視覚的に表示されます。
-
バッファの表示:
(display-buffer (current-buffer) 'display-buffer-reuse-window)
: カバレッジ情報が適用されたバッファをEmacsウィンドウに表示します。display-buffer-reuse-window
は、可能であれば既存のウィンドウを再利用して表示することを試みます。
この一連の処理により、Goのコードカバレッジ情報がEmacs内で直接、視覚的に分かりやすい形で提供され、開発ワークフローが改善されます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語のテストに関するドキュメント: https://go.dev/doc/code#Testing
go tool cover
に関するドキュメント: https://pkg.go.dev/cmd/go/internal/test/cover (Goの内部パッケージですが、go tool cover
の動作について記述があります)- Emacs Lispの公式マニュアル: https://www.gnu.org/software/emacs/manual/html_node/elisp/
- Emacsのフェイスに関するドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Faces.html
- Emacsのオーバーレイに関するドキュメント: https://www.gnu.org/software/emacs/manual/html_node/elisp/Overlays.html
参考にした情報源リンク
- Go Gerrit Change 12684043 (元のコードレビュー): https://golang.org/cl/12684043
go test -coverprofile
の出力形式に関する情報 (Goのソースコードや関連ドキュメントから推測)- Emacs Lispの関数や概念に関する一般的な知識 (Emacs Lisp Reference Manualなど)
go tool cover
のHTML出力の視覚的表現 (色の使用など)