2018年12月11日火曜日

Azure Pipelines でマトリックスを組んでみた

この記事はAzure DevOps Advent Calendar 2018 11日目の記事です。
4日目に投稿した「ブログズミ: Azure Pipelines 始めました」の続きになります。


早速ですが、上のスクリーンショットは 31 個のジョブを実行している様子です。
いやホント10 並列は素晴らしいですね!

では、今回はマトリックスを組んでみた流れを紹介したいと思います。
テンプレートを作る
まずは、パラメータで処理パターンを増やすためのベースとなるテンプレート部分を作ります。
ドキュメントを見た感じですと、ジョブ、もしくはステップをテンプレートにできるようです。
Job and step templates - Azure Pipelines & TFS | Microsoft Docs

テンプレートの記述と利用
テンプレートは azure-pipelines.yml とは 別の yaml ファイルに記述します。
そして、azure-pipelines.yml もしくは別のテンプレートから下記のように参照します。
1
2
steps:
  - template: templates/sample.yml  # Template reference
1
2
jobs:
  - template: templates/jobs.yml  # Template reference

サンプルがドキュメントに載ってますので、そちらを見ていただくとわかりやすいと思います。
テンプレートは別のリポジトリのファイルを参照することもできるようですが、今回は使わないので省略します。 > Using other repositories

テンプレートにパラメータを渡す
テンプレートにパラメータとして任意の要素を渡し、パラメータに応じて処理内容を変えたいと思います。
Passing parameters
(ドキュメントを見ていると、単純の値や配列だけでなく、ジョブをパラメータとして扱うこともできるようですが、まだ実践してないので今回は省略)

まずは、パラメータのやり方から。

template 側の yaml
1
2
3
4
5
6
7
8
9
parameters:
  name: ''
  options: ''
  vmImage: 'ubuntu 16.04' # デフォルト値
 
jobs:
  - job: ${{ parameters.name }}
    pool:
      vmImage: ${{ parameters.vmImage }}
parameters にパラメータを定義します。ここでセットした値はデフォルト値になります。
パラメータの値を使用する場合は、${{ parameters.hogehoge }} のようにします。

azure-pipelines.yml 側
1
2
3
4
jobs:
  - template: .ci/azure_pipelines/template-make-test.yml
    parameters:
      name: default
パラメータを渡す場合も、parameters に記述します。
これでパラメータの受け渡しができるようになりました。

パラメータに応じて処理を変える
さて、中核に迫ってまいりました。
パラメータの内容によって、処理を変える方法はいろいろ考えられます。
ドキュメントを見ていると、高度なことができそうな感じがしているのですが・・・
まだ使いこなせてないので、省略。
* Template expression functions
   Expressions
* Insertion
* Conditional insertion
* Iterative insertion

今回はとてもシンプルにパラメータをスクリプトの引数として使いました。
(Azure Pipelines 側の書き方や制約に縛られず、自分の好きな書き方をできるので、まずはこの方法で良いと思います。)

iutest では以下のようにしました。
1
2
3
4
5
6
7
8
9
10
11
parameters:
  options: ''
  package_name: 'default'
 
steps:
- script: make -C test -j4 OUTPUTXML=junit ${{ parameters.options }}
  displayName: 'make'
- script: make -C test -j4 OUTPUTXML=junit ${{ parameters.options }} ${run_option} test
  displayName: 'test'
  env:
    run_option: RUN_OPTION="--iutest_default_package_name=${{ parameters.package_name }}"

make にオプションを渡せるようにしたのと、テスト実行時のオプションを指定するようにしました。

azure-pipelines.ym > job template > step template
さて、ここまででほぼほぼテンプレートを作ることができるようになっていると思いますが、最後にテンプレートを2段構成にしてみました。
テンプレートをテンプレートで使うことも問題なくできるのです!

iutest では、make test とテスト結果を集計するステップテンプレート、
そして、それをある程度の粒度でパラメタライズドしたジョブテンプレートにしました。



azrue-pipelines.yml は以下のように、ジョブテンプレートを利用するだけの非常にシンプルな内容になりました。
1
2
3
4
5
6
7
jobs:
  - template: .ci/azure_pipelines/template-make-test.yml
    parameters:
      name: default
  - template: .ci/azure_pipelines/template-make-disabled-test.yml
    parameters:
      name: disabled

テンプレート側の yaml は長いので、GitHub を見てくださいmm
iutest/azure-pipelines.yml at master · srz-zumix/iutest
iutest/.ci/azure_pipelines at master · srz-zumix/iutest

マトリックスを書く

これが、ちょっと長いのですが・・・こんな感じになりました。
parameters:
name: ''
options: ''
vmImage: 'ubuntu 16.04'
jobs:
- job: ${{ parameters.name }}
strategy:
matrix:
Disable_AnyParamTest:
DISABLED_CONFIG: IUTEST_HAS_ANY_PARAM_TEST
Disable_AssertionNoEqualToObject:
DISABLED_CONFIG: IUTEST_HAS_ASSERTION_NOEQUALTO_OBJECT
Disable_AssertionReturn:
DISABLED_CONFIG: IUTEST_HAS_ASSERTION_RETURN
Disable_AutoFixtureParamTest:
DISABLED_CONFIG: IUTEST_HAS_AUTOFIXTURE_PARAM_TEST
Disable_Exception:
DISABLED_CONFIG: IUTEST_HAS_EXCEPTIONS
Disable_GenRand:
DISABLED_CONFIG: IUTEST_HAS_GENRAND
# Disable_IgnoreTest:
# DISABLED_CONFIG: IUTEST_HAS_IGNORE_TEST
Disable_Matchers:
DISABLED_CONFIG: IUTEST_HAS_MATCHERS
Disable_MatchersAllOfAndAnyOf:
DISABLED_CONFIG: IUTEST_HAS_MATCHER_ALLOF_AND_ANYOF
Disable_MatchersElementsAre:
DISABLED_CONFIG: IUTEST_HAS_MATCHER_ELEMENTSARE
Disable_MatchersElementsAreArrayForward:
DISABLED_CONFIG: IUTEST_HAS_MATCHER_ELEMENTSAREARRAYFORWARD
Disable_MatchersRegex:
DISABLED_CONFIG: IUTEST_HAS_MATCHER_REGEX
Disable_Package:
DISABLED_CONFIG: IUTEST_HAS_PACKAGE
Disable_Peep:
DISABLED_CONFIG: IUTEST_HAS_PEEP
Disable_PeepClass:
DISABLED_CONFIG: IUTEST_HAS_PEEP_CLASS
Disable_PeepFunc:
DISABLED_CONFIG: IUTEST_HAS_PEEP_FUNC
Disable_PeepStaticFunc:
DISABLED_CONFIG: IUTEST_HAS_PEEP_STATIC_FUNC
Disable_PrintTo:
DISABLED_CONFIG: IUTEST_HAS_PRINT_TO
Disable_Regex:
DISABLED_CONFIG: IUTEST_HAS_REGEX
Disable_ReportSkipped:
DISABLED_CONFIG: IUTEST_HAS_REPORT_SKIPPED
Disable_Socket:
DISABLED_CONFIG: IUTEST_HAS_SOCKET
Disable_SpiLambdaSupport:
DISABLED_CONFIG: IUTEST_HAS_SPI_LAMBDA_SUPPORT
Disable_StaticAssertTypeEq:
DISABLED_CONFIG: IUTEST_HAS_STATIC_ASSERT_TYPEEQ
Disable_StreamCapture:
DISABLED_CONFIG: IUTEST_HAS_STREAM_CAPTURE
Disable_StreamResult:
DISABLED_CONFIG: IUTEST_HAS_STREAM_RESULT
Disable_TestFixtureAliasByTuple:
DISABLED_CONFIG: IUTEST_HAS_TESTFIXTURE_ALIAS_BY_TUPLE
# Disable_TestNameAliasJp:
# DISABLED_CONFIG: IUTEST_HAS_TESTNAME_ALIAS_JP
Disable_TestNameAlias:
DISABLED_CONFIG: IUTEST_HAS_TESTNAME_ALIAS
Disable_LambdaStatements:
DISABLED_CONFIG: IUTEST_HAS_LAMBDA_STATEMENTS
Disable_TypedTestP:
DISABLED_CONFIG: IUTEST_HAS_TYPED_TEST_P
Disable_TypedTest:
DISABLED_CONFIG: IUTEST_HAS_TYPED_TEST
Disable_TypedTestPStrict:
DISABLED_CONFIG: IUTEST_TYPED_TEST_P_STRICT
# feature parame test configs
Disable_Combine:
DISABLED_CONFIG: IUTEST_HAS_COMBINE
Disable_Concat:
DISABLED_CONFIG: IUTEST_HAS_CONCAT
Disable_CsvParams:
DISABLED_CONFIG: IUTEST_HAS_CSVPARAMS
Disable_Pairwise:
DISABLED_CONFIG: IUTEST_HAS_PAIRWISE
Disable_ParamMethodTest:
DISABLED_CONFIG: IUTEST_HAS_PARAM_METHOD_TEST
Disable_ParamTest:
DISABLED_CONFIG: IUTEST_HAS_PARAM_TEST
Disable_ParamTestParamNameGenerator:
DISABLED_CONFIG: IUTEST_HAS_PARAM_TEST_PARAM_NAME_GENERATOR
Disable_RandomValues:
DISABLED_CONFIG: IUTEST_HAS_RANDOMVALUES
Disable_ValuesGen:
DISABLED_CONFIG: IUTEST_HAS_VALUESGEN
Disable_VariadicCombine:
DISABLED_CONFIG: IUTEST_HAS_VARIADIC_COMBINE
Disable_VariadicPariwise:
DISABLED_CONFIG: IUTEST_HAS_VARIADIC_PAIRWISE
Disable_VariadicValues:
DISABLED_CONFIG: IUTEST_HAS_VARIADIC_VALUES
# spec config
Disable_BitwiseExpressionDecompose:
DISABLED_CONFIG: IUTEST_HAS_BITWISE_EXPRESSION_DECOMPOSE
Disable_FileStat:
DISABLED_CONFIG: IUTEST_HAS_FILE_STAT
Disable_FOpen:
DISABLED_CONFIG: IUTEST_HAS_FOPEN
Disable_StringStream:
DISABLED_CONFIG: IUTEST_HAS_STRINGSTREAM
Disable_Tuple:
DISABLED_CONFIG: IUTEST_HAS_TUPLE
Disable_VariadicTemplates:
DISABLED_CONFIG: IUTEST_HAS_VARIADIC_TEMPLATES
pool:
vmImage: ${{ parameters.vmImage }}
steps:
- template: template-make-test-steps.yml
parameters:
options: ${{ parameters.options }} DEFS+=-D${DISABLED_CONFIG}=0
package_name: "${{ parameters.name }}-${DISABLED_CONFIG}"


ステップテンプレートに make のオプションを生成して流しているジョブテンプレートになるのですが、
やっていることは1つのマクロを定義して config を変えて、ビルド・テストをさせています。
そのマクロ部分をマトリックスにしたのですが、いちいち名前をつけないといけないのが面倒・・・
DISABLED_CONFIG 配列に定義できると楽なんですが・・・

たぶん、もっとキレイに書けるんだろうなーという感触はありますが、今後の課題としたいと思いますmm

10並列の効果はどんなもんよ?
iutest の現時点で、1 pipeline で 57 ジョブが実行しています。
各ジョブでやっていることは、ほぼほぼ同等でマクロ定義が1つ異なるだけのビルド・テストです。
1ジョブの実行時間が約5分。

直列に行った場合は、5 x 57 分で約5時間です・・・

が、これが、


なんと!


およそ 35分 !!
10並列のパワー素晴らしい

テスト失敗してますよ?
はい。
今まで config 変更のテストは、ローカルの Jenkins もしくは今はなき SnapCI でやっていたのですが(それかローカル実行)、とにかく時間がかかって辛かった!のでやってませんでした。すみませんmm
今回、Azure Pipelines の力によってメンテナンスできてなかった部分が炙り出された感じです。
本当、ありがとうございます!!って感じです。
(FAIL はちゃんと直します。ごめんなさい)

今後やりたいこと
さて、今回はこれで以上となりますが、
まだまだ Azure Pipelines でやりたいこと・できることは多そうです。
また進展あったらまとめたいと思います。ではでは。

* [ci skip]
* auto cancel
* 簡易テスト後に、マトリックスが走るようにする(dependsOn の挿入?)
* 条件分岐(xml 出力がされないことを期待する場合に、集計をしない)
* yaml に書く量を減らしたい
* 本当のマトリックス( N x M )で爆発させたい

0 件のコメント:

コメントを投稿