Help us understand the problem. What is going on with this article?

Vim の "make test" を読み解く 第 5 回 (runtest.vim)

More than 1 year has passed since last update.

Vim の new-style-testing を実行している張本人の runtest.vim に、いよいよ潜入していきたいと思います。まずはファイル先頭部分の訳。

このスクリプトはテスト付きの .vim ファイルを開いているときに読み込まれます。スクリプトが正常終了すると .res ファイルが生成されます。エラーは test.log ファイルに追加されます。

特定のテスト関数のみを実行するには 2 つめの引数を追加します。それは Test_ 関数の名前と一致します。

出力は "messages" ファイルの中に見つけることができます。

テストスクリプトには何でも含むことができますが、"Test_" で始まる関数だけは特別です。これらは呼び出されるので、アサート関数を含んでいるべきです。例として test_assert.vim を参照してください。

"Test_" 関数を含む他のファイルを読み込むことも可能です。これは Vim を再起動する必要がないので、テストを早くすることができます。ただし、テストがお互いに干渉しないように留意してください。

エラーがアサート関数によって適切に検出できない場合は、エラーを v:errors リストに追加してください。

各 Test_ 関数に準備が必要な場合は、SetUp 関数を定義します。これは各 Test_ 関数の前に呼び出されます。

各 Test_ 関数の後にクリーンアップが必要な場合は、TearDown 関数を定義します。これは各 Test_ 関数の後に呼び出されます。

テストをデバッグするときには v:errors にメッセージを追加するのが便利です。

" Support function: get the alloc ID by name.
function GetAllocId(name)

これは、第 3 回 に出てきた test_alloc_fail() を呼ぶ際に、fail させるメモリ割り当ての ID を取得するための関数ですね。

func RunTheTest(test)

ファイル中の 1 つのテストを実行するメインの関数

  • 初期設定いろいろ
  • SetUp が存在しているときはそれを呼ぶ
  • "Test_nocatch_" で始まるときはエラーを自分でハンドルする
  • 意図せず Vim が終了した時の処理を VimLeavePre で設定
  • テスト関数を呼び出し
  • skipped や error が起きたときの処理
  • TearDown が存在しているときはそれを呼ぶ
  • すべての autocommand を消して swap の処理だけ設定する
  • 不要なタブやウィンドウを消す
func AfterTheTest()

ファイル中の 1 つのテストの終わりの処理

  • v:errors が 1 以上であれば、
    • s:fail のカウント、メッセージの出力
    • テスト関数で見つかったエラーは v:errors に入るので、それを s:errors に渡して v:errors をクリア
func EarlyExit(test)

VimLeavePre の自動コマンドに割り当てられている。Vim が意図せず終了した時に v:errors を追加している。

" This function can be called by a test if it wants to abort testing.
func FinishTesting()

テストを中止したいときに呼ぶこともできるが、通常はすべてのテストを実行した後に呼ばれて後処理を行う。

  • クリーンアップとか
  • s:fail が 0 であれば .res ファイルを作成
  • s:errors があれば test.log に書き込む
  • いくつのテストが実行されたか表示
  • FAILED や SKIPPED の表示、messages への書き込み
  • Vim を終了させる
let s:done = 0
let s:fail = 0
let s:errors = []
let s:messages = []
let s:skipped = []
  • s:done : 実行されたテストの数
  • s:fail : failしたテストの数
  • s:errors : errorの内容、s:messagesにも追加される
  • s:messages : テスト中のメッセージの表示、エラー以外も含む
  • s:skipped : スキップされた内容の表示
if expand('%') =~ 'test_vimscript.vim'
  " this test has intentional s:errors, don't use try/catch.
  source %
else
  try
    source %
  catch
    let s:fail += 1
    call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
  endtry
endif

Vim の引数としてファイルを開いただけだと、単に開いただけで Vim script として読み込まれていない (テストファイル内の関数等が認識されていない) のでここで読み込む。

" Names of flaky tests.
let s:flaky_tests = [
      \ 'Test_call()',
      \ 'Test_channel_handler()',
      \ 'Test_client_server()',
      \ ....
      \ 'Test_zz1_terminal_in_gui()',
      \ ]

" Pattern indicating a common flaky test failure.
let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump'

"flaky" をなんと訳すのがいいのか分かりませんが、タイミングやリソースの状態?によっては fail する可能性のあるテストのリストです。詳細は後述します。s:flaky_errors_re の関数を実行中に fail した場合には一律 flaky として扱われるようです。

" Locate Test_ functions and execute them.
redir @q
silent function /^Test_
redir END
let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))

" If there is an extra argument filter the function names against it.
if argc() > 1
  let s:tests = filter(s:tests, 'v:val =~ argv(1)')
endif

:function の出力結果を利用して、"Test_" から始まる関数の一覧を s:tests に作っています。もしくは引数で特定のテストが与えられている場合はそれ単体のみに filter() しています。

" Execute the tests in alphabetical order.
for s:test in sort(s:tests)
  ....

  call RunTheTest(s:test)

  " Repeat a flaky test.  Give up when:
  " - it fails again with the same message
  " - it fails five times (with a different mesage)
  if len(v:errors) > 0
        \ && (index(s:flaky_tests, s:test) >= 0
        \      || v:errors[0] =~ s:flaky_errors_re)
    while 1
      ....

      if run_nr == 5 || prev_error == v:errors[0]
        call add(total_errors, 'Flaky test failed too often, giving up')
        let v:errors = total_errors
        break
      endif

      ....

      call RunTheTest(s:test)

      if len(v:errors) == 0
        " Test passed on rerun.
        break
      endif
    endwhile
  endif

  call AfterTheTest()
endfor

call FinishTesting()

各テストをアルファベット順に実行していきます。たまに "Test_zz" で始まる名前のテストがありますが、最後に実行したいという意図のようですね。
RunTheTest() でテストを実行します。v:errors が空でなくて (fail していて) かつ flaky テストの場合、

  • 同じメッセージで 2 回 fail する
  • もしくは 5 回 fail する

まで繰り返します。
各テストの終わりに AfterTheTest() を実行し、対象のテストをすべて実行し終えたら最後に FinishTesting() を実行しています。

第 5 回はここまで。

m_nish
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした