ようへいの日々精進XP

よかろうもん

WordPress のプラグインバージョン番号の付け方に翻弄された 〜 そして、shellspec を使ってみた 〜

tl;dr

inokara.hateblo.jp

これの続きです。

やっぱり、WordPress プラグインバージョン

当初は...

WordPressプラグインバージョンをパースする処理を以下のように書いていました。

echo ${plugin_name} | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+)?" | sed 's/.//'

これは、プラグインのバージョン番号が以下のような状態を想定した処理でした。

  • plugin-name.1.2.3.zip
  • plugin-name.12345678.zip
  • plugin-name-7.1.2.3.zip

実際に実行すると、以下のように、意図した通りにバージョン番号 (1.2.3 とか 12345678 等) が取得出来ます。

$ echo plugin-name.1.2.3.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+)?" | sed 's/.//'
1.2.3
$ echo plugin-name.12345678.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+)?" | sed 's/.//'
12345678
$ echo plugin-name-7.1.2.3.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+)?" | sed 's/.//'
1.2.3

とーころが...

新手が登場しました。

  • plugin-name.1.2.3.4.zip

これを従来のパース処理で処理しようとすると...

$ echo plugin-name.1.2.3.4.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+)?" | sed 's/.//'
1.2.3

冒頭の 3 桁のみがパースされる状態となりました...

改修、そして shellspec

正規表現の試行錯誤

正規表現検定 8 級なので、とりあえず試行錯誤。試行錯誤の結果、以下のように書くことで、なんとか意図したような結果を得ることが出来ました。

$ echo ${plugin_name} | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+|\.[0-9]+\.[0-9]+)?" | sed 's/.//'

先程の 4 桁バージョン番号を処理してみます。

$ echo plugin-name.1.2.3.4.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+|\.[0-9]+\.[0-9]+)?" | sed 's/.//'
1.2.3.4
$ echo plugin-name-100.1.2.3.4.zip | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+|\.[0-9]+\.[0-9]+)?" | sed 's/.//'
1.2.3.4

意図した通りに解析されていることを確認しました。

そして shellspec (1)

今後も同じような変異種が現れる可能性もあり、試行錯誤している間にデグレしてしまう可能性もあったので、この処理を関数化してユニットテストを書くようにしました。

そこで、テストフレームワークとして shellspec を選びました。

github.com

選んだ理由としては、特に強いこだわりがあったわけではありませんが、Rspec っぽく書けるという触れ込みだったので、お試し程度に使ってみようと思った次第です。

インストールの手順等は割愛しますが、今回は macOS に homebrew でインストールして利用しました。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H114
$ brew tap shellspec/shellspec
$ brew install shellspec

以下のように実行して、shellspec を初期化します。

$ mkdir project
$ cd project
$ shellspec --init

project ディレクトリ直下に .shellspec ファイルと spec ディレクトリ、spec ディレクトリ以下に spec_helper.sh ファイルが作成されます。

そして shellspec (2)

以下のようにテスト対象の関数を用意しました。

$ cd project
$ vim lib/version-number-parse
function version_number_parse {
  echo $1 | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+|\.[0-9]+\.[0-9]+)?" | sed 's/.//'
}

そして、テストは以下のように用意しました。

$ vim spec/version-number-parse_spec.sh
Describe "Check for version-number-parse script"
  Include lib/version-number-parse
  Describe "Call version_number_parse"
    It "plugin-name.3.1.21.11.zip"
      When call version_number_parse plugin-name.3.1.21.11.zip
      The output should eq "3.1.21.11"
    End

    It "plugin-name.3.10.2.zip"
      When call version_number_parse plugin-name.3.10.2.zip
      The output should eq "3.10.2"
    End

    It "plugin-name.3.9.2.zip"
      When call version_number_parse plugin-name.3.9.2.zip
      The output should eq "3.9.2"
    End

    It "plugin-name.1.2.zip"
      When call version_number_parse plugin-name.1.2.zip
      The output should eq "1.2"
    End

    It "plugin-name-7.1.2.3.zip"
      When call version_number_parse plugin-name-7.1.2.3.zip
      The output should eq "1.2.3"
    End

    It "plugin-name.12345678.zip"
      When call version_number_parse plugin-name.12345678.zip
      The output should eq "12345678"
    End
  End
End

後は、以下のように実行します。

$ shellspec --format document -s bash

以下のように出力されました。

$ cd project
$ shellspec --format document -s bash
Running: /bin/bash [bash 3.2.57(1)-release]

Check for version-number-parse script
  Call version_number_parse
    plugin-name.3.1.21.11.zip
    plugin-name.3.10.2.zip
    plugin-name.3.9.2.zip
    plugin-name.1.2.zip
    plugin-name-7.1.2.3.zip
    plugin-name.12345678.zip

Finished in 0.37 seconds (user 0.31 seconds, sys 0.07 seconds)
6 examples, 0 failures

Fail させてみると、以下のように出力されました。

$ shellspec --format document -s bash
Running: /bin/bash [bash 3.2.57(1)-release]

Check for version-number-parse script
  Call version_number_parse
    plugin-name.3.1.21.11.zip
    plugin-name.3.10.2.zip
    plugin-name.3.9.2.zip (FAILED - 1)
    plugin-name.1.2.zip
    plugin-name-7.1.2.3.zip
    plugin-name.12345678.zip

Examples:
  1) Check for version-number-parse script Call version_number_parse plugin-name.3.9.2.zip
     When call version_number_parse plugin-name.3.9.2.zip

     1.1) The output should eq 3.9.1

            expected: "3.9.1"
                 got: "3.9.2"

          # spec/ver-check_spec.sh:16

Finished in 0.33 seconds (user 0.31 seconds, sys 0.07 seconds)
6 examples, 1 failure


Failure examples / Errors: (Listed here affect your suite's status)

shellspec spec/ver-check_spec.sh:14 # 1) Check for ver-check script Call get_current_version plugin-name.3.9.2.zip FAILED

いい感じ!shellspec

テスト結果の出力

以下のように --format オプションで指定可能です。

# --format を指定しない場合
$ shellspec -s bash
Running: /bin/bash [bash 3.2.57(1)-release]
......

Finished in 0.32 seconds (user 0.31 seconds, sys 0.07 seconds)
6 examples, 0 failures

# junit フォーマットで取得
$ shellspec --format junit -s bash
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="6" time="0.32" errors="0" failures="0" name="ver-check">
  <testsuite id="0" tests="6" errors="0" failures="0" skipped="0" name="spec/version-number-parse_spec.sh" hostname="oreno-mac" timestamp="2021-01-05T15:22:57">
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name.3.1.21.11.zip"></testcase>
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name.3.10.2.zip"></testcase>
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name.3.9.2.zip"></testcase>
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name.1.2.zip"></testcase>
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name-7.1.2.3.zip"></testcase>
    <testcase time="0" classname="spec/version-number-parse_spec.sh" name="Check for version-number-parse script Call version_number_parse plugin-name.12345678.zip"></testcase>
  </testsuite>
</testsuites>

テストを実行するシェルを指定する

デフォルトでは、/bin/sh が使われるとのことですが、--shell 又は -s オプションでシェルを指定することが出来ます。

$ shellspec -s bash

上記の例では、その名の通り、bash を利用することになります。

CircleCI のオフィシャル Docker イメージである、cimg/python:3.6 上で shellspec を利用しようとした際に、このイメージのデフォルトシェルは /bin/sh で、その実体は dash となっており、function がサポートしていないことでエラーとなりテストが実行されずに焦りました。

circleci@0c366a26ccfd:~/project$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Apr  3  2020 /bin/sh -> dash

circleci@0c366a26ccfd:/project$ cat lib/version-number-parse
function version_number_parse {
  echo $1 | grep -o -E "(\.[0-9]+|\.[0-9]+\.){1}[0-9]+(\.[0-9]+|\.[0-9]+\.[0-9]+)?" | sed 's/.//'
}

# dash だと function がサポートされていない
circleci@0c366a26ccfd:/project$ shellspec
/bin/sh: 1: lib/version-number-parse: function: not found

Unexpected output to stderr occurred at line 1-2 in 'spec/version-number-parse_spec.sh'
Running: /bin/sh [sh]


Finished in 0.16 seconds (user 0.04 seconds, sys 0.01 seconds)
0 examples, 0 failures, aborted by an unexpected error

Aborted with status code [executor: 127] [reporter: 1] [error handler: 102]
Fatal error occurred, terminated with exit status 102.

circleci@0c366a26ccfd:/project$ shellspec --shell bash
Running: /bin/bash [bash 4.4.20(1)-release]
......

Finished in 0.27 seconds (user 0.23 seconds, sys 0.06 seconds)
6 examples, 0 failures

上記のように --shell bash を追加して実行することで、テストが正常に実行されました。

以上

WordPressプラグインバージョンの付け方に翻弄されてしまいましたが、シェルスクリプトでもテストを書くと良いということと、shellspec に出会うことが出来て良かったというお話でした。

参考

github.com

qiita.com

qiita.com