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

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

このコミットは、Go言語の継続的インテグレーション(CI)ダッシュボードアプリケーションに対する機能強化、バグ修正、およびテストの拡充を目的としています。具体的には、Go以外の外部パッケージのビルド結果を追跡・表示するための新しい/packagesハンドラの追加、既存のビルド結果追跡ロジックの改善、そしてこれらの変更を検証するための広範なテストケースの追加が行われています。これにより、ダッシュボードの機能性と堅牢性が向上し、Goエコシステム全体のビルド状況をより包括的に把握できるようになりました。

コミット

commit 4aab04178dcfd977ef6b2ccad85de37c044d4d81
Author: Andrew Gerrand <adg@golang.org>
Date:   Tue Nov 29 19:24:57 2011 +1100

    dashboard: more tests, bug fixes, and /packages handler

    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/5441053

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

https://github.com/golang/go/commit/4aab04178dcfd977ef6b2ccad85de37c044d4d81

元コミット内容

dashboard: more tests, bug fixes, and /packages handler

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5441053

変更の背景

Go言語プロジェクトは、その開発初期から継続的インテグレーション(CI)を重視しており、様々なプラットフォームでのビルドとテストの結果を監視するためのダッシュボードアプリケーションを運用していました。このダッシュボードは、Go本体のリポジトリだけでなく、Goエコシステム内の重要な外部パッケージのビルド健全性も追跡する必要がありました。

このコミットが行われた2011年当時、Go言語はまだ比較的新しい言語であり、そのエコシステムは急速に成長していました。多くの開発者がGoで新しいライブラリやアプリケーションを開発し始めており、それらの互換性やビルドの安定性をGo本体の変更と合わせて確認することが重要でした。

既存のダッシュボードはGo本体のビルド結果を主に扱っていましたが、外部パッケージのビルド結果をより詳細に、かつGo本体の特定のコミットハッシュと紐付けて管理する機能が求められていました。また、ダッシュボード自体の堅牢性を高めるために、既存機能のテストカバレッジを向上させ、発見されたバグを修正する必要がありました。

このコミットは、これらの要求に応えるために、以下の主要な目的を持って実施されました。

  1. 外部パッケージの監視強化: Go本体の特定のバージョンに対する外部パッケージのビルド結果を正確に追跡できるようにする。
  2. ダッシュボードの機能拡張: 監視対象の外部パッケージのリストをプログラム的に取得できるAPIエンドポイントを提供する。
  3. 品質向上: 既存のロジックのバグを修正し、新しい機能を含むダッシュボード全体のテストカバレッジを向上させる。

これにより、Go開発チームはGo本体と外部パッケージの間の依存関係や互換性の問題を早期に発見し、Goエコシステム全体の健全性を維持できるようになりました。

前提知識の解説

このコミットの変更内容を理解するためには、以下の技術的背景知識が役立ちます。

  • Go言語: Googleによって開発された静的型付けのコンパイル型言語。並行処理に強みを持つ。
  • Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)。ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォーム。Go言語はGAEの標準サポート言語の一つです。
    • app.yaml: App Engineアプリケーションの設定ファイル。ルーティングルール、環境変数、スケーリング設定などを定義します。このファイルは、どのURLパスがどのスクリプト(Goアプリケーションの場合は_go_app)によって処理されるかを指定するために使用されます。
    • Datastore: Google Cloud Datastore(現在はFirestore in Datastore mode)は、GAEアプリケーションが利用できるNoSQLドキュメントデータベースサービスです。Goのappengine/datastoreパッケージを通じてアクセスされます。エンティティ(データレコード)とキー(一意の識別子)の概念を持ち、エンティティグループによるトランザクションもサポートします。
  • HTTPハンドラとルーティング: Go言語の標準ライブラリnet/httpは、HTTPサーバーを構築するための強力な機能を提供します。
    • http.HandleFunc: 特定のURLパスに対するHTTPリクエストを処理する関数(ハンドラ)を登録するために使用されます。
    • http.ResponseWriter: HTTPレスポンスをクライアントに書き込むためのインターフェース。
    • *http.Request: クライアントからのHTTPリクエストに関する情報(URL、ヘッダー、ボディなど)を含む構造体。
  • url.Values: net/urlパッケージの一部で、URLのクエリパラメータ(例: ?key=value&another=param)をキーと値のマップとして扱うための型です。
  • JSONエンコーディング/デコーディング: encoding/jsonパッケージは、Goのデータ構造とJSON形式の間で変換を行うための機能を提供します。ウェブAPIでデータをやり取りする際によく使用されます。
  • 継続的インテグレーション (CI): ソフトウェア開発手法の一つで、開発者がコードの変更を共有リポジトリに頻繁にマージし、自動化されたビルドとテストを実行することで、統合の問題を早期に発見し解決することを目指します。Goダッシュボードは、このCIプロセスの一部として機能します。
  • コミットハッシュとリポジトリ: Gitなどのバージョン管理システムにおけるコミットを一意に識別するためのハッシュ値。Goダッシュボードでは、Go本体のコミットハッシュ(GoHash)と、外部パッケージのコミットハッシュ(Hash)を区別して管理しています。
  • パッケージパス (PackagePath): Go言語におけるパッケージのインポートパス。例えば、"code.google.com/p/go.more"のような形式で、外部パッケージを一意に識別するために使用されます。

これらの概念を理解することで、コミットがGoダッシュボードの機能、データモデル、およびテスト戦略にどのように影響を与えているかを深く把握することができます。

技術的詳細

このコミットは、Goダッシュボードアプリケーションの3つの主要なファイルに影響を与えています。

  1. misc/dashboard/app/app.yaml:

    • このファイルはGoogle App Engineアプリケーションのデプロイ設定を定義します。
    • 変更点: handlersセクションの- url: /(commit|tag|todo|result)行が- url: /(commit|package|result|tag|todo)に変更されました。
    • これにより、新しく追加される/packagesエンドポイントがApp EngineによってGoアプリケーション(_go_app)にルーティングされるようになりました。これは、ダッシュボードが監視するパッケージのリストを公開するためのAPIエンドポイントを有効にするための必須の変更です。
  2. misc/dashboard/app/build/build.go:

    • このファイルは、ダッシュボードの主要なビジネスロジックとデータモデル(Commit, Result, Packageなど)を定義しています。
    • Commit.HasGoHashResultメソッドの追加:
      func (com *Commit) HasGoHashResult(builder, goHash string) bool {
          for _, r := range com.Result {
              p := strings.SplitN(r, "|", 4)
              if len(p) == 4 && p[0] == builder && p[3] == goHash {
                  return true
              }
          }
          return false
      }
      
      この新しいメソッドは、特定のbuilder(例: linux-386)と特定のGoコミットハッシュ(goHash)に対応するビルド結果が、与えられたCommitエンティティに存在するかどうかをチェックします。これは、Go本体の特定のバージョンに対する外部パッケージのビルド結果を正確にフィルタリングするために導入されました。既存のHasResultがビルダのみを考慮していたのに対し、goHashも考慮することで、より粒度の細かい結果の検索が可能になります。
    • todoHandlerの修正:
      • todoHandlerは、特定のビルダがまだビルドまたはテストを行っていないコミット("todo"リスト)を返す役割を担っています。
      • 変更前はgoHashフィルタリングが不完全でした。変更後、goHashパラメータが指定されている場合にCommit.HasGoHashResultを使用して、より正確な未完了タスクの特定を行うようになりました。
      • 具体的には、if !com.HasResult(builder)の行が、if goHash != "" { hasResult = com.HasGoHashResult(builder, goHash) } else { hasResult = com.HasResult(builder) }という条件分岐に置き換えられました。これにより、goHashが指定された場合はそのGoバージョンに紐づく結果を、そうでない場合は一般的な結果をチェックするようになりました。
    • packagesHandler関数の追加:
      func packagesHandler(w http.ResponseWriter, r *http.Request) {
          c := appengine.NewContext(r)
          var pkgs []*Package
          for t := datastore.NewQuery("Package").Run(c); ; {
              pkg := new(Package)
              if _, err := t.Next(pkg); err == datastore.Done {
                  break
              } else if err != nil {
                  logErr(w, r, err)
                  return
              }
              if pkg.Path != "" { // Only include non-Go packages
                  pkgs = append(pkgs, pkg)
              }
          }
          if err := json.NewEncoder(w).Encode(pkgs); err != nil {
              logErr(w, r, err)
          }
      }
      
      この新しいハンドラは、DatastoreからPackageエンティティをクエリし、Pathフィールドが空でない(つまりGo本体ではない)パッケージのリストをJSON形式で返します。これにより、ダッシュボードが監視している外部パッケージのリストをプログラム的に取得できるようになります。
    • init関数でのルーティング登録:
      • http.HandleFunc("/packages", AuthHandler(packagesHandler))が追加され、/packagesパスが新しく定義されたpackagesHandlerにマッピングされました。AuthHandlerは認証を必要とするハンドラであることを示唆しています。
  3. misc/dashboard/app/build/test.go:

    • このファイルは、ダッシュボードアプリケーションのテストケースを定義しています。
    • テストデータの拡充:
      • testPkg定数とtestPackagetestPackages変数が定義され、非Goパッケージのテストデータが用意されました。
      • testRequestsスライスに、新しいエンドポイントと機能に対応する多数のテストケースが追加されました。
        • /packagesエンドポイントのテスト: {"/packages", nil, nil, []*Package{testPackage}}が追加され、packagesHandlerが正しく動作し、監視対象のパッケージリストを返すことを検証します。
        • Goリポジトリのテストの拡充: ResultOKフィールドの修正(falseからtrueへ)、ログ関連のテスト(/log/...)の追加、複数のビルダでのテストケースの追加など。
        • ブランチ関連のテスト: 異なる親ハッシュを持つコミット(ブランチ)のテストケースが追加され、ダッシュボードがブランチを正しく処理できることを検証します。
        • 非Goリポジトリのテスト: PackagePathGoHashを明示的に指定した/commit/resultのテストケースが追加されました。これにより、外部パッケージのビルド結果がGo本体の特定のバージョンと紐づけて正しく記録・検索されることを検証します。
    • テストヘルパーの改善: testHandler内のエラー処理で、errorf呼び出し後にreturnを追加することで、テストがエラー発生時に即座に終了し、後続の不必要な処理を防ぐようになりました。これにより、テストの信頼性とデバッグのしやすさが向上します。

これらの変更は、GoダッシュボードがGoエコシステム全体の健全性をより包括的に監視し、開発者がGo本体の変更が外部パッケージに与える影響をより容易に把握できるようにするための重要なステップでした。

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

misc/dashboard/app/app.yaml

- url: /(commit|package|result|tag|todo)
  script: _go_app

misc/dashboard/app/build/build.go

Commit.HasGoHashResult メソッドの追加

func (com *Commit) HasGoHashResult(builder, goHash string) bool {
	for _, r := range com.Result {
		p := strings.SplitN(r, "|", 4)
		if len(p) == 4 && p[0] == builder && p[3] == goHash {
			return true
		}
	}
	return false
}

todoHandler の変更

	var nextHash string
	for t := q.Run(c); nextHash == ""; {
		com := new(Commit)
		if _, err := t.Next(com); err == datastore.Done {
			break
		} else if err != nil {
			logErr(w, r, err)
			return
		}
		var hasResult bool
		if goHash != "" {
			hasResult = com.HasGoHashResult(builder, goHash)
		} else {
			hasResult = com.HasResult(builder)
		}
		if !hasResult {
			nextHash = com.Hash
		}
	}
	fmt.Fprint(w, nextHash)

packagesHandler 関数の追加

func packagesHandler(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var pkgs []*Package
	for t := datastore.NewQuery("Package").Run(c); ; {
		pkg := new(Package)
		if _, err := t.Next(pkg); err == datastore.Done {
			break
		} else if err != nil {
			logErr(w, r, err)
			return
		}
		if pkg.Path != "" {
			pkgs = append(pkgs, pkg)
		}
	}
	if err := json.NewEncoder(w).Encode(pkgs); err != nil {
		logErr(w, r, err)
	}
}

init 関数でのルーティング登録

	http.HandleFunc("/packages", AuthHandler(packagesHandler))

misc/dashboard/app/build/test.go

テストデータの追加と testRequests の拡充

const testPkg = "code.google.com/p/go.more"

var testPackage = &Package{Name: "Test", Path: testPkg}

var testPackages = []*Package{
	&Package{Name: "Go", Path: ""},
	testPackage,
}

var testRequests = []struct {
	path string
	vals url.Values
	req  interface{}
	res  interface{}
}{
	// Packages
	{"/packages", nil, nil, []*Package{testPackage}},

	// ... (既存のテストケースに加えて、以下のテストケースが追加・修正)

	// non-Go repos
	{"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1001", ParentHash: "1000"}, nil},
	{"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1002", ParentHash: "1001"}, nil},
	{"/commit", nil, &Commit{PackagePath: testPkg, Hash: "1003", ParentHash: "1002"}, nil},
	{"/todo", url.Values{"builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, "1003"},
	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0001", OK: true}, nil},
	{"/todo", url.Values{"builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, "1002"},
	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1002", GoHash: "0001", OK: true}, nil},
	{"/todo", url.Values{"builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, "1001"},
	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0001", OK: true}, nil},
	{"/todo", url.Values{"builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, ""},
	{"/todo", url.Values{"builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0002"}}, nil, "1003"},
}

コアとなるコードの解説

app.yaml の変更

app.yamlの変更は、Google App Engineが新しい/packagesエンドポイントへのリクエストをGoアプリケーションに正しくルーティングできるようにするために不可欠です。これにより、クライアント(ブラウザや他のサービス)がこのURLにアクセスした際に、Goアプリケーション内のpackagesHandlerが呼び出されるようになります。これは、新しいAPI機能を提供する上で最初のステップとなります。

Commit.HasGoHashResult メソッド

このメソッドは、GoダッシュボードがGo本体の特定のコミットハッシュ(goHash)と関連付けられた外部パッケージのビルド結果を追跡できるようにするために導入されました。以前は、ビルド結果は単にビルダ名とコミットハッシュ(外部パッケージ自体のハッシュ)に基づいていましたが、Go本体の変更が外部パッケージに与える影響を正確に評価するためには、どのGo本体のバージョンでビルドされたかという情報が必要でした。

r"builder|packagePath|hash|goHash"のような形式の文字列を想定しており、strings.SplitNで分割して各要素を抽出します。そして、与えられたbuildergoHashが結果文字列の対応する部分と一致するかをチェックします。これにより、ダッシュボードはGo本体の特定のバージョンに対する外部パッケージのビルド健全性をより正確に判断できるようになります。

todoHandler の変更

todoHandlerは、特定のビルダがまだ処理していないコミット(ビルドまたはテストが完了していないもの)を特定する役割を担っています。この変更により、todoHandlergoHashクエリパラメータを考慮するようになりました。

  • もしリクエストにgoHashパラメータが含まれていれば、新しく追加されたCommit.HasGoHashResultメソッドを使用して、その特定のGoハッシュとビルダに対応する結果が存在するかどうかをチェックします。
  • goHashが指定されていない場合は、既存のCommit.HasResultメソッド(ビルダのみを考慮)を使用します。

このロジックの改善により、ダッシュボードはGo本体の特定のバージョンに関連する未完了のビルドタスクをより正確に特定し、表示できるようになりました。これは、特に外部パッケージのビルド状況をGo本体の特定の変更と関連付けて監視する際に重要です。

packagesHandler 関数

packagesHandlerは、ダッシュボードが監視しているGo以外のパッケージのリストをJSON形式で返す新しいHTTPハンドラです。

  1. appengine.NewContext(r): App Engineのコンテキストを取得します。Datastoreへのアクセスにはこのコンテキストが必要です。
  2. datastore.NewQuery("Package").Run(c): DatastoreからPackageエンティティをクエリします。Packageエンティティは、監視対象の各パッケージに関する情報(名前、パスなど)を格納していると推測されます。
  3. ループで各Packageエンティティを取得し、pkg.Path != ""の条件でフィルタリングします。Pathが空のパッケージはGo本体を意味すると考えられるため、ここではGo以外の外部パッケージのみを対象とします。
  4. 取得したパッケージのリストをjson.NewEncoder(w).Encode(pkgs)でJSON形式にエンコードし、HTTPレスポンスとしてクライアントに書き込みます。

このハンドラは、ダッシュボードのフロントエンドや他のツールが、監視対象の外部パッケージのリストを動的に取得するためのAPIを提供します。これにより、ダッシュボードの柔軟性と拡張性が向上します。

init 関数でのルーティング登録

http.HandleFunc("/packages", AuthHandler(packagesHandler))の追加は、/packagesというURLパスへのHTTPリクエストがpackagesHandler関数によって処理されるように、GoのHTTPサーバーにルーティングルールを登録します。AuthHandlerは、このエンドポイントへのアクセスが認証を必要とすることを示しており、セキュリティが考慮されています。

test.go の変更

test.goの変更は、上記の新しい機能と修正されたロジックが正しく動作することを保証するためのものです。

  • テストデータの追加: testPkgtestPackageなどの変数は、非Goパッケージのテストシナリオを構築するために使用されます。これにより、Go本体以外のリポジトリのビルド結果をシミュレートできます。
  • testRequestsの拡充:
    • /packagesエンドポイントのテストケースは、packagesHandlerが期待通りにJSON形式のパッケージリストを返すことを検証します。
    • 非Goリポジトリのテストケースは、PackagePathGoHashを明示的に指定することで、todoHandlerresultHandlerが外部パッケージのビルド結果をGo本体の特定のバージョンと関連付けて正しく処理できることを検証します。これは、Commit.HasGoHashResultのロジックが正しく機能していることを確認する上で重要です。
    • 既存のGoリポジトリのテストケースも、より現実的なシナリオ(例: 複数のビルダ、ブランチ、ログのテスト)をカバーするように拡充され、ダッシュボード全体の堅牢性が向上しました。

これらのテストは、変更が意図した通りに機能し、既存の機能に回帰バグを導入していないことを確認するための重要な安全網となります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にmisc/dashboardディレクトリ)
  • Google App Engineの当時のドキュメント(一般的な概念理解のため)
  • Go言語の標準ライブラリのドキュメント
  • Gitのコミットログと差分表示
  • Go言語の継続的インテグレーションに関する一般的な知識