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

[インデックス 16819] ファイルの概要

このコミットは、Go言語のregexpパッケージにおけるTestRE2Exhaustiveというテストの実行方法を最適化するものです。具体的には、このテストがGoのレース検出器(race detector)が有効な状態で実行される際に発生するタイムアウト問題を解決するため、レース検出器が有効な場合にはこのテストがスキップされるように変更されました。これにより、ビルド時間の短縮と、ビルドサーバーでのタイムアウトの回避が図られています。

コミット

  • コミットハッシュ: 9bfb69187fc76cce47032111f4cbe055a28704ae
  • Author: David Symonds dsymonds@golang.org
  • Date: Fri Jul 19 23:44:22 2013 +1000

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9bfb69187fc76cce47032111f4cbe055a28704ae

元コミット内容

regexp: exclude exhaustive RE2 test from running under race detector.

It is an expensive test to run, and even more so with -race,
and causes timeouts on builders. It is doubtful that it would
find a race that other tests in this package wouldn't, so there
is little loss in excluding it.

Update #5837.

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/11568043

変更の背景

この変更の背景には、Go言語のregexpパッケージに含まれるTestRE2Exhaustiveというテストが、特にレース検出器(-raceフラグ)を有効にして実行された場合に、非常に高い計算コストを要し、ビルドサーバーでタイムアウトを引き起こすという問題がありました。

Goのビルドシステムでは、コードの品質と安定性を保証するために、様々な環境(異なるOS、アーキテクチャ、Goのバージョン、そしてレース検出器の有無など)でテストが実行されます。TestRE2Exhaustiveは、正規表現エンジンRE2の網羅的なテストケースを実行するため、元々実行時間が長いテストでした。これに加えて、レース検出器はプログラムのメモリアクセスを監視し、データ競合(race condition)を検出するための追加のオーバーヘッドを発生させます。このオーバーヘッドがTestRE2Exhaustiveの実行時間をさらに大幅に増加させ、結果としてビルドサーバーの許容時間を超えてタイムアウトしてしまう事態が発生していました。

コミットメッセージには「他のテストが検出しないような競合をこのテストが検出する可能性は低い」と明記されており、このテストをレース検出器下で実行しないことによる品質低下のリスクは低いと判断されました。そのため、ビルドの効率性と安定性を優先し、この特定のテストをレース検出器の実行から除外する決定がなされました。これは、Issue #5837で報告された問題への対応でもあります。

前提知識の解説

Go言語のregexpパッケージとRE2

Go言語の標準ライブラリには、正規表現を扱うためのregexpパッケージが含まれています。このパッケージは、Googleが開発した高性能な正規表現エンジンであるRE2ライブラリに基づいています。RE2は、線形時間(入力文字列の長さに比例する時間)で動作することを保証し、バックトラッキングによる指数関数的な実行時間の増加を防ぐという特徴を持っています。これは、一般的な正規表現エンジンが持つ可能性のある脆弱性(ReDoS: Regular expression Denial of Service)に対する耐性があることを意味します。TestRE2Exhaustiveのようなテストは、このRE2エンジンの網羅的な動作検証を目的としています。

Goのレース検出器(Race Detector)

Go言語には、プログラム実行中に発生するデータ競合(race condition)を検出するための組み込みツールである「レース検出器」があります。データ競合とは、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生するバグです。データ競合はプログラムの予測不能な動作やクラッシュの原因となることがあり、デバッグが非常に困難です。

レース検出器は、Goプログラムをgo run -racego build -race、またはgo test -raceのように-raceフラグを付けてコンパイル・実行することで有効にできます。有効にすると、実行時にメモリアクセスを監視し、競合が検出された場合に詳細なレポートを出力します。ただし、この監視には実行時オーバーヘッドが伴い、プログラムの実行速度が低下します。

Goのビルドタグ(Build Tags)

Go言語では、ソースコードファイルに「ビルドタグ」と呼ばれる特別なコメントを追加することで、特定の条件が満たされた場合にのみそのファイルをコンパイルに含める、あるいは除外することができます。ビルドタグは、ファイルの先頭に// +build tagnameのような形式で記述されます。

複数のタグを指定する場合、スペースで区切るとAND条件(例: // +build linux amd64はLinuxかつAMD64の場合にコンパイル)となり、カンマで区切るとOR条件(例: // +build debug,releaseはdebugまたはreleaseの場合にコンパイル)となります。また、タグ名の前に!を付けることで否定条件を指定できます(例: // +build !windowsはWindows以外の場合にコンパイル)。

この機能は、OSやアーキテクチャ固有のコード、デバッグ用コード、あるいは特定のテスト環境でのみ実行したいコードなどを管理するのに非常に有用です。

技術的詳細

このコミットの技術的な核心は、Goのビルドタグ(build tags)を利用して、特定のテストファイルがレース検出器が有効な環境下でコンパイルされないようにすることです。

変更前は、TestRE2Exhaustiveテストはsrc/pkg/regexp/exec_test.goファイル内に他のテストと一緒に定義されていました。このファイルは、通常のテスト実行時にも、レース検出器が有効なテスト実行時にもコンパイルされていました。

変更後、TestRE2Exhaustiveテストはsrc/pkg/regexp/exec_test.goから削除され、新たに作成されたsrc/pkg/regexp/exec2_test.goファイルに移動されました。この新しいファイルexec2_test.goの先頭には、以下のビルドタグが追加されています。

// +build !race

この// +build !raceというディレクティブは、Goコンパイラに対して「このファイルは、raceというビルドタグが有効でない場合にのみコンパイルに含める」という指示を与えます。

Goのテスト実行時に-raceフラグが指定されると、Goツールチェーンは内部的にraceというビルドタグを有効にします。したがって、-raceフラグが指定された場合、exec2_test.goファイルはコンパイル対象から除外されます。これにより、TestRE2Exhaustiveテストはレース検出器が有効な環境では実行されなくなります。

一方、-raceフラグが指定されない通常のテスト実行時には、raceビルドタグは有効にならないため、exec2_test.goは通常通りコンパイルされ、TestRE2Exhaustiveテストも実行されます。

このアプローチにより、テストの網羅性を損なうことなく、ビルドサーバーでのタイムアウト問題を効率的に解決しています。テストコード自体に変更を加えることなく、コンパイル時の条件によってテストの実行を制御できる点が、この解決策の洗練された部分です。

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

このコミットでは、主に2つのファイルが変更されています。

  1. src/pkg/regexp/exec2_test.go (新規作成)

    • このファイルが新規作成され、TestRE2Exhaustive関数がここに移されました。
    • ファイルの先頭にビルドタグ// +build !raceが追加されています。
    --- /dev/null
    +++ b/src/pkg/regexp/exec2_test.go
    @@ -0,0 +1,20 @@
    +// Copyright 2013 The Go Authors. All rights reserved.
    +// Use of this source code is governed by a BSD-style
    +// license that can be found in the LICENSE file.
    +
    +// +build !race
    +
    +package regexp
    +
    +import (
    +	"testing"
    +)
    +
    +// This test is excluded when running under the race detector because
    +// it is a very expensive test and takes too long.
    +func TestRE2Exhaustive(t *testing.T) {
    +	if testing.Short() {
    +		t.Skip("skipping TestRE2Exhaustive during short test")
    +	}
    +	testRE2(t, "testdata/re2-exhaustive.txt.bz2")
    +}
    
  2. src/pkg/regexp/exec_test.go (変更)

    • 既存のTestRE2Exhaustive関数がこのファイルから削除されました。
    --- a/src/pkg/regexp/exec_test.go
    +++ b/src/pkg/regexp/exec_test.go
    @@ -67,13 +67,6 @@ func TestRE2Search(t *testing.T) {
     	testRE2(t, "testdata/re2-search.txt")
     }
     
    -func TestRE2Exhaustive(t *testing.T) {
    -	if testing.Short() {
    -		t.Skip("skipping TestRE2Exhaustive during short test")
    -	}
    -	testRE2(t, "testdata/re2-exhaustive.txt.bz2")
    -}
    -
     func testRE2(t *testing.T, file string) {
     	f, err := os.Open(file)
     	if err != nil {
    

コアとなるコードの解説

このコミットの核となる変更は、TestRE2Exhaustiveテスト関数を新しいファイルsrc/pkg/regexp/exec2_test.goに移動し、そのファイルの先頭に// +build !raceというビルドタグを追加した点です。

// +build !race ディレクティブ

この行はGoのビルドシステムに対する指示です。

  • +build は、続く文字列がビルドタグであることを示します。
  • !race は、「raceというビルドタグが有効でない場合にのみ、このファイルをコンパイルに含める」という意味です。

Goのテストを実行する際にgo test -raceと指定すると、Goツールチェーンは内部的にraceというビルドタグを有効にします。このとき、exec2_test.goファイルの!raceという条件が満たされなくなるため、このファイルはコンパイル対象から除外されます。結果として、TestRE2Exhaustiveテストはレース検出器が有効な環境では実行されません。

一方、go testのように-raceフラグなしでテストを実行する場合、raceビルドタグは有効になりません。この場合、!raceという条件が満たされるため、exec2_test.goは通常通りコンパイルされ、TestRE2Exhaustiveテストも実行されます。

TestRE2Exhaustive関数の移動

TestRE2Exhaustive関数自体は、元のexec_test.goから新しいexec2_test.goにそのまま移動されました。関数のロジック自体に変更はありません。このテストは、testdata/re2-exhaustive.txt.bz2という圧縮されたデータファイルを使用して、RE2正規表現エンジンの網羅的なテストケースを実行します。testing.Short()フラグが有効な場合はスキップされる既存のロジックも維持されています。

この変更により、高コストなTestRE2Exhaustiveテストがレース検出器のオーバーヘッドと組み合わさってビルドサーバーをタイムアウトさせる問題を回避しつつ、通常のテスト実行時には引き続きこの重要な網羅的テストが実行されることが保証されます。これは、テストの網羅性とビルド効率のバランスを取るための効果的な解決策です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(ビルドタグ、テスト、レース検出器に関する情報)
  • RE2正規表現エンジンの概要
  • データ競合(Race Condition)に関する一般的な情報
  • Go言語のテストに関する記事やチュートリアル
  • GitHubのgolang/goリポジトリのコミット履歴とIssueトラッカー