Swift Digest
ST-0029 | Swift Evolution↗

イベントストリームに追加の issue メタデータを含める

Include additional issue metadata in event stream

Proposal
ST-0029↗
Authors
Jerry Chen
Review Manager
Rachel Brindle
Status
Accepted

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。

01 何が問題だったのか

Swift Testing は、テスト実行中に発生したイベントを JSON のイベントストリーム(event stream)として出力します。これは Xcode や VS Code といった外部ツールがテスト結果を読み取るための、安定したインターフェースです。テスト中に問題を検出すると Swift Testing は “issue recorded” イベントを発行しますが、そこに含まれる issue のメタデータはごく限られていて、issue の種類を区別できるだけの構造化された情報がありませんでした。

たとえば次の 2 つのテストは、それぞれ異なる原因で失敗します。

@Test func `Issue A`() async throws {
    Issue.record("Issue A")
}
@Test func `Issue B`() async throws {
    struct ErrorB: Error {}
    throw ErrorB()
}

ところがイベントストリームに出力される issue のメタデータは、sourceLocation を除けばほぼ同じ内容になってしまいます。

"issue": {
  "isFailure": true,
  "isKnown": false,
  "severity": "error",
  "sourceLocation": { ... }
}

これでは、ツール側で「エラーが throw された」「#expect が失敗した」「confirmation の回数が合わなかった」といった失敗の内容を区別できず、ユーザーに豊かなコンテキストを提示できません。

さらに、Swift Testing のイベントを消費するツールは、他の言語やテストライブラリのイベントも扱えるようにしたいという要望があります。そのため、Swift 専用ではなく、他のテストライブラリでも再利用できる汎用的な issue スキーマが求められていました。

02 どのように解決されるのか

イベントストリームのスキーマバージョン 6.5 で、issue イベントに新しいフィールドを追加し、いくつかの既存フィールドの扱いを見直します。これにより、ツールは issue の種類を機械的に判別し、失敗の詳細を構造化された形で受け取れるようになります。

issue に追加される新しいフィールド

issue の内容に応じて、次のフィールドが追加されます。

  • error: エラーが throw されたときに、そのエラーの情報(code / domain / description と型情報)を格納します。
  • confirmationMiscount: confirmation が期待した回数と異なる回数だけ確認されたときの、実際の回数(actual)と期待した回数(expected)を格納します。expected は単一の値のほか、範囲(min / max)でも表現できます。
  • exceededTimeLimit: テストが time limit を超過したときに、超過した制限値を秒単位で格納します。
  • expression: issue に紐づく式。主に失敗した expectation(#expect)の式そのものを、ソースコード・評価値・型情報・部分式とともに捉えます。

たとえばエラーが throw された場合は、次のように error フィールドが含まれます。

"issue": {
  "error": {
    "code": 1,
    "description": "SampleError()",
    "domain": "SampleTests...SampleError",
    "type": {
      "fullyQualifiedName": "SampleTests.SampleError",
      "unqualifiedName": "SampleError"
    }
  },
  "isFailure": true,
  "severity": "error"
}

confirmation(expectedCount: 1...5) に対して 10 回確認された、といった miscount は次のように表現されます。

"issue": {
  "confirmationMiscount": {
    "actual": 10,
    "expected": { "min": 1, "max": 5 }
  },
  "isFailure": true,
  "severity": "error"
}

これらのフィールドの有無から、ツールは issue の種類を導けます(たとえば confirmationMiscount があれば confirmation の回数不一致だとわかります)。

既存フィールドの変更

  • isKnown: これまで必須の真偽値でしたが、任意のフィールドになり、既定では false として扱われます。withKnownIssue に人間向けのコメントを渡していた場合は、真偽値の代わりにそのコメント文字列が入ります。

    "issue": {
      "isFailure": false,
      "isKnown": "This matches and doesn't fail",
      "severity": "error"
    }
    
  • messages: これまで issue の人間向け説明を格納していましたが、今回追加されるフィールドからツール側で説明を再構成できるため、冗長になります。そこで任意のフィールドになり、既定では出力されなくなります(生成とシリアライズのコストを避けるためです)。従来どおり messages を受け取りたいツールは、環境変数 SWIFT_TESTING_EVENT_STREAM_MESSAGES_FIELD_ENABLED を設定します。

すべてのイベント共通のフィールド

  • comments: テストの作者が指定したコメントを格納する、イベント共通のフィールドです。Issue.record("A comment") や #expect(a == b, "A comment") のように渡したコメントがここに入ります。なお withKnownIssue に渡したコメントはここには含まれず、前述の isKnown に入ります。
  • sourceLocation: これまで issue の中にあったソース位置は、値の添付やスキップされたテストケースなど他の種類のイベントでも使えるよう、イベント(<event>)の最上位に移動します。

ツール側での対応

6.4 スキーマからの主な非互換は次の 3 点です。それ以外のフィールドはすべて追加のみで、既存の解釈に影響しません。

  • isKnown が必須の真偽値ではなく、任意の真偽値または文字列になる。
  • messages が任意になる(既定で出力されない)。
  • sourceLocation が個々のイベント構造から <event> の最上位へ移動する。

Swift Testing と連携している既存ツールは、これらの新しいフィールドを読むように連携コードを更新する必要があります。

03 今後の見通し

以下は将来の構想として挙げられているもので、この Proposal で実現が約束されているわけではありません。

issue のバックトレース

issue のバックトレースを任意のフィールドとして含めることが検討されました。しかし現時点では、Linux 上で Issue のバックトレースのアドレスをシンボル化(symbolication)する手段がまだなく、またプロセス内でのシンボル化はコストの高い処理のため、設計には追加の検討が必要です。これらの課題が解決すれば、後続の Proposal でバックトレースフィールドを追加できる可能性があります。

例外情報

Swift 自体はエラー処理に例外(exception)を使いませんが、テストコードは Objective-C や Java のように例外を使う言語と相互運用することがあります。そうした例外情報を <issue> に含めることも検討されています。error フィールドが例外のメタデータを置く候補になり得ますが、例外は Foundation の NSError に由来するエラーが持つフィールド(code / domain / type)の多くを共有しないため、そのまま流用できるとは限りません。