Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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の設定ファイル(.emacsinit.el)で変数の値を変更できます。
  • defface: Emacsのテキスト表示における「フェイス」(フォント、色、背景色などの属性の集合)を定義するために使用されます。これにより、特定のテキストに視覚的なスタイルを適用できます。
  • overlay: Emacsの強力な機能の一つで、バッファのテキストの上に視覚的な属性(色、フォント、ツールチップなど)を重ねて表示するために使用されます。オーバーレイは実際のバッファテキストを変更しないため、一時的なハイライトや情報表示に適しています。
  • clone-indirect-buffer: 既存のバッファの「間接的なクローン」を作成する関数です。間接的なバッファは、元のバッファと同じテキスト内容を共有しますが、独自のポイント、マーク、オーバーレイ、モードラインなどを持ちます。これにより、同じファイルの内容を異なる表示設定で同時に見ることができます。

技術的詳細

このコミットの主要な技術的詳細は、go-mode.elgo test -coverprofileの出力を解析し、Emacsのオーバーレイ機能を使って視覚化するロジックを追加した点にあります。

  1. カバレッジデータの読み込みと解析:

    • go-coverage関数は、go test -coverprofileによって生成されたカバレッジファイルのパスを引数として受け取ります。
    • with-temp-bufferを使って一時バッファにカバレッジファイルの内容を読み込みます。
    • カバレッジファイルは、各行がファイル名:開始行.開始列,終了行.終了列 実行回数のような形式で記述されています。例えば、main.go:10.5,12.20 1 1は、main.goの10行目5列から12行目20列までのコードブロックが1回実行されたことを意味します。
    • split-stringmapcar 'string-to-numberdestructuring-bindといったEmacs Lispの関数を駆使して、このテキストデータを解析し、各カバレッジブロックのファイル名、開始/終了の行/列、そして実行回数(カバーされたかどうか)を抽出します。
    • 抽出されたデータは、新しく定義されたgo--covered構造体(defstructで定義)のインスタンスとしてリストに格納されます。この構造体は、カバレッジ範囲の開始/終了位置と、その範囲がカバーされたかどうかを示すブール値(covered)を保持します。
  2. 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によって表示されます。
  3. ユーティリティ関数の追加とリファクタリング:

    • go--goto-line関数が新しく追加され、指定された行番号にカーソルを移動する処理をカプセル化しています。これにより、既存のコードベースで複数回出現していたgoto-char (point-min)forward-line (1- line)の組み合わせが共通化され、コードの可読性と保守性が向上しています。
    • go--line-column-to-point関数は、行番号と列番号からEmacsバッファのポイント(文字位置)を計算するユーティリティ関数です。これは、カバレッジデータの解析結果をEmacsのオーバーレイ機能に適用するために不可欠です。

コアとなるコードの変更箇所

misc/emacs/go-mode.elファイルが変更されています。

主な変更点は以下の通りです。

  1. フェイス定義の追加:

    (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つの新しいフェイスが定義されています。

  2. ユーティリティ関数の追加:

    (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)))
    

    行番号への移動と、行・列からポイントへの変換を行うヘルパー関数が追加されています。

  3. カバレッジデータ構造の定義:

    (defstruct go--covered
      start-line start-column end-line end-column covered)
    

    カバレッジ範囲の情報を保持するための構造体が定義されています。

  4. 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))))
    

    この関数がカバレッジファイルを読み込み、解析し、オーバーレイを適用して表示する主要なロジックを含んでいます。

  5. 既存コードのリファクタリング: go-play-buffergo-unused-imports-linesgo-mode-find-file-at-pointなどの既存の関数内で、行移動のロジックが新しく定義されたgo--goto-line関数を使用するように変更されています。

コアとなるコードの解説

このコミットの核となるのは、go-mode.elに追加されたgo-coverage関数です。

  1. go-coverage関数のインタフェースと初期設定:

    • (interactive "fCoverage file: ")により、この関数がEmacsのM-xコマンドとして実行可能になり、ユーザーにカバレッジファイルのパスを入力するプロンプトが表示されます。
    • letフォーム内で、カバレッジ範囲を格納するリストranges、現在のバッファのファイル名file-name、カバレッジ表示用バッファの名前gocov-buffer-nameが初期化されます。
  2. カバレッジファイルの読み込みと解析:

    • (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(真)となります。
  3. カバレッジ表示バッファの準備:

    • (with-current-buffer (or (get-buffer gocov-buffer-name) (clone-indirect-buffer gocov-buffer-name nil)) ...):
      • まず、既にカバレッジ表示用のバッファが存在するかどうかをget-bufferで確認します。
      • 存在しない場合は、現在のGoソースコードバッファの「間接的なクローン」をclone-indirect-bufferで作成します。間接的なクローンは元のバッファと同じテキスト内容を共有しますが、独自のオーバーレイを持つことができます。
      • このカバレッジ表示用バッファを現在のバッファとして設定します。
  4. オーバーレイの適用:

    • (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のオーバーレイが上書きされ、正確なカバレッジ情報が視覚的に表示されます。
  5. バッファの表示:

    • (display-buffer (current-buffer) 'display-buffer-reuse-window): カバレッジ情報が適用されたバッファをEmacsウィンドウに表示します。display-buffer-reuse-windowは、可能であれば既存のウィンドウを再利用して表示することを試みます。

この一連の処理により、Goのコードカバレッジ情報がEmacs内で直接、視覚的に分かりやすい形で提供され、開発ワークフローが改善されます。

関連リンク

参考にした情報源リンク

  • Go Gerrit Change 12684043 (元のコードレビュー): https://golang.org/cl/12684043
  • go test -coverprofileの出力形式に関する情報 (Goのソースコードや関連ドキュメントから推測)
  • Emacs Lispの関数や概念に関する一般的な知識 (Emacs Lisp Reference Manualなど)
  • go tool coverのHTML出力の視覚的表現 (色の使用など)