読者コミュニティ|runn クックブック
本の感想や質問やレシピ追加依頼など、無料公開範囲に限らず、お気軽にコメントしてください。
![@tyamahori](https://res.cloudinary.com/zenn/image/fetch/s---6ew4Hnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/463c5cc3ab.jpeg)
どうぞよろしくお願いいたします
ブック購入させていただきました。
テストの書き方について質問がございます。
非同期にDBレコードの値が変わるAPIのテストで使ってみたいと思いインストールしました。
下記のようなステップのテストを記述しています。
・API呼び出し
・DB参照
現状ですとAPI呼び出し後、DB参照がすぐに行われ、ステータスが変わる前の値を検証してしまいテストが通りません。
DB参照前に数秒Sleepさせることは可能なのでしょうか?
こんにちは。購入ありがとうございます。
DB参照前に数秒Sleepさせること
おそらく実施したいことは「ステータスが変わることを検証したい」だと思われます。
2つ紹介します。
例として「APIコール後に users
テーブルの status
カラムの値が 0
から別の値( 1
とか 2
など)になること」を確認するケースを考えます。
1. 単純にsleepさせる
Execランナーをつかって sleep
コマンドを挟みます。
steps:
-
req:
/path/to/endpoint:
post:
# (snip)
-
exec:
command: sleep 5
-
db:
query: SELECT status FROM users;
test: current.rows[0].status != 0
2. ステータスが変化するまで確認する
loop:
セクションを使用してリトライをします。
steps:
-
req:
/path/to/endpoint:
post:
# (snip)
-
db:
query: SELECT status FROM users;
loop:
interval: 5sec # 失敗したら5秒待つ
count: 3 # 3回リトライする
until: current.rows[0].status != 0
リトライについては「ステップのリトライを設定する 」で紹介していますのでご覧ください。
ご返信ありがとうございます。
execランナーがまさにやりたいことでした。
ご回答ありがとうございます。
![calloc134](https://res.cloudinary.com/zenn/image/fetch/s--EpRgGZEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/340e84e5b1.jpeg)
初めまして。かろっくと申します。
昨日からrunnを使わせていただいております。このrunnの使い方について、いくつか質問失礼します。
runn run dummy.yml
を実行した際に、一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?
また、ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?
よろしくお願いいたします。
質問ありがとうございます!
一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?
v0.64.1時点ではありません。シナリオの成功=シナリオ内の全ステップの成功(スキップ除く)なのでステップの成功失敗の表示はありません。
ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?
v0.64.1時点ではありません。ステップの失敗が発生すると即シナリオ失敗となっています。
なお、後者の質問に対してですが、現在オプションの作成を検討しておりそのための実装を追加している最中です。
そうすると必然的に前者の質問(ステップごとの失敗)についても対応していくことになるのではないかと思います。
![calloc134](https://res.cloudinary.com/zenn/image/fetch/s--EpRgGZEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/340e84e5b1.jpeg)
ご返信ありがとうございます!実装お待ちしています。
ステップが一つ失敗したとき、以降のテストが実行されませんが、以降のテストが実行されるようなオプション等は存在しますか?
こちらは v0.66.0 で force:
セクションで実現できるようになりましたので共有しておきます。
![calloc134](https://res.cloudinary.com/zenn/image/fetch/s--EpRgGZEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/340e84e5b1.jpeg)
開発お疲れ様です!
一つのシナリオごとにテストの成功/失敗を表示していますが、ステップごとの成功の可否を表示することはできますでしょうか?
こちらは v0.68.0 で --verbose
オプションを付与して runn run
を実行することで確認できるようになりましたので共有します。
![calloc134](https://res.cloudinary.com/zenn/image/fetch/s--EpRgGZEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/340e84e5b1.jpeg)
ありがとうございます!!開発お疲れ様です!
![calloc134](https://res.cloudinary.com/zenn/image/fetch/s--EpRgGZEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/340e84e5b1.jpeg)
OpenAPIの定義からrunnのシナリオテストを生成するためのユーティリティを書いてみました。
おお!これは便利ツールだ!ありがとうございます!
runn自体にあっても良い機能ですね!
![harachan](https://res.cloudinary.com/zenn/image/fetch/s--SNMrSxql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8485cd2a72.jpeg)
素敵なツールありがとうございます!!
「Homebrewでインストールする」の内容が brewコマンドを用いたものではなく go installしているものだったので誤植かなと思い報告させていただきます
ありがとうございますー修正しました!
![harachan](https://res.cloudinary.com/zenn/image/fetch/s--SNMrSxql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8485cd2a72.jpeg)
修正ありがとうございます!!
![@tyamahori](https://res.cloudinary.com/zenn/image/fetch/s---6ew4Hnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/463c5cc3ab.jpeg)
いつもありがとうございます。
こちらのページですが、--concurrentのコマンドがタイポしているように見受けられます。
ご確認をお願いできますと嬉しいです。どうぞよろしくお願いいたします。
$ runn run path/to/**/*.yml --concurrnt on
ありがとうございます!修正しました!
![びきニキ](https://res.cloudinary.com/zenn/image/fetch/s--zLKFK1qQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c5e280830.jpeg)
はじめまして!1つ質問させてください。
Sessionベースの認証が必要なAPIを突破する方法はありますか?見落としていたらすみません🙇
お手隙のタイミングで教えていただけると幸いです!よろしくお願いします。
Sessionベースといっても一概に1つの実装に限定できるわけではありません。
例えば、クライアント側にSession IDを渡すことで認証状態を維持しているのであれば、そのSession IDの取得方法とSession IDの受け渡し方法をrunnで模倣できれば可能だと思います。
PHPマニュアルでもSession IDの受け渡し方法として2つ紹介されています。
クライアント側にSession IDを受け渡すことができればいいので、実装方法も、上記2つに限らないでしょう。
例えばPHPアプリケーションだと(何も設定を変えていなければ) PHPSESSID
という名前のCookieを経由してSession IDをブラウザ側に保存しているはずです。
![びきニキ](https://res.cloudinary.com/zenn/image/fetch/s--zLKFK1qQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/3c5e280830.jpeg)
参考URLまでありがとうございます。仰る通り「クライアント側にSession IDを渡すことで認証状態を維持」という方法で実装しているようなので、runnで模倣する形で実装してみようと思います。ふわふわした内容の質問で失礼しました…!
![togana](https://res.cloudinary.com/zenn/image/fetch/s--wXhC00wu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0e626620cc.jpeg)
シナリオベースのテストを試してみたくて runn
を利用してみているのですが、自動生成されたファイルで実行エラーとなり原因がわからないので質問させてください。
runn クックブック を参考にランブックを作成して実行しています。
実行時に invalid char escape (10:2306)
となってしまうのですが、レスポンスに含まれていてはいけない文字などありますか?
実行した内容としては下記になります。
$ runn -v
runn version 0.80.2
-
runn new
コマンドで--out
と--and-run
オプションを利用してtest
付きのランファイルを作成する
$ runn new --and-run --out runbook.yml -- curl http://localhost/api/tasks -H "accept: application/json"
- 作成されたファイルを見る
$ cat ./runbook.yml
desc: Generated by `runn new`
runners:
req: http://localhost
steps:
- req:
/api/tasks:
get:
headers:
Accept: application/json
body: null
test: |
current.res.status == 200
&& current.res.headers['Access-Control-Allow-Origin'][0] == "*"
&& current.res.headers['Cache-Control'][0] == "no-cache, private"
&& current.res.headers['Content-Type'][0] == "application/json"
&& 'Date' in current.res.headers
&& current.res.headers['Host'][0] == "localhost"
&& current.res.headers['X-Powered-By'][0] == "PHP/8.2.8"
&& current.res.headers['X-Ratelimit-Limit'][0] == "60"
&& current.res.headers['X-Ratelimit-Remaining'][0] == "59"
&& compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})
- 実行する
$ runn run ./runbook.yml
F
1) ./runbook.yml 636eb5202577df2773e016cec9ed364eb920d41c
Failure/Error: test failed on 'Generated by `runn new`'.steps[0]: invalid char escape (10:2306)
| && compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})
| .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................^
Failure step (./runbook.yml):
5 - req:
6 /api/tasks:
7 get:
8 headers:
9 Accept: application/json
10 body: null
11 test: |
12 current.res.status == 200
13 && current.res.headers['Access-Control-Allow-Origin'][0] == "*"
14 && current.res.headers['Cache-Control'][0] == "no-cache, private"
15 && current.res.headers['Content-Type'][0] == "application/json"
16 && 'Date' in current.res.headers
17 && current.res.headers['Host'][0] == "localhost"
18 && current.res.headers['X-Powered-By'][0] == "PHP/8.2.8"
19 && current.res.headers['X-Ratelimit-Limit'][0] == "60"
20 && current.res.headers['X-Ratelimit-Remaining'][0] == "59"
21 && compare(current.res.body, {"data":[{"id":"01h7j21q12erkf1fwna29shhw7","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.082000Z","content_changed_at":"2023-08-11T10:25:06.083000Z","complete_changed_at":"2023-08-11T10:25:06.085000Z"},{"id":"01h7j21q0xwrh65qdwcskh30kn","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.077000Z","content_changed_at":"2023-08-11T10:25:06.078000Z","complete_changed_at":"2023-08-11T10:25:06.081000Z"},{"id":"01h7j21q0r8x1rnqx63gy4mt4w","content":"test2","is_completed":true,"created_at":"2023-08-11T10:25:06.072000Z","content_changed_at":"2023-08-11T10:25:06.073000Z","complete_changed_at":"2023-08-11T10:25:06.076000Z"},{"id":"01h7j21q0kccqz8dyk6y6tmv0q","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.067000Z","content_changed_at":"2023-08-11T10:25:06.068000Z","complete_changed_at":"2023-08-11T10:25:06.071000Z"},{"id":"01h7j21q0fwham459j2y614tp6","content":"test4","is_completed":true,"created_at":"2023-08-11T10:25:06.063000Z","content_changed_at":"2023-08-11T10:25:06.064000Z","complete_changed_at":"2023-08-11T10:25:06.066000Z"},{"id":"01h7j21q0agq9t4kb6zc4w1kff","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.058000Z","content_changed_at":"2023-08-11T10:25:06.060000Z","complete_changed_at":"2023-08-11T10:25:06.061000Z"},{"id":"01h7j21q057934evj87arm9qfn","content":"test4","is_completed":false,"created_at":"2023-08-11T10:25:06.053000Z","content_changed_at":"2023-08-11T10:25:06.055000Z","complete_changed_at":"2023-08-11T10:25:06.056000Z"},{"id":"01h7j21q00atfjtw7dwa0cxdea","content":"test1","is_completed":false,"created_at":"2023-08-11T10:25:06.048000Z","content_changed_at":"2023-08-11T10:25:06.051000Z","complete_changed_at":"2023-08-11T10:25:06.052000Z"},{"id":"01h7j21pzvgdw9njfrcmb3gy2g","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.043000Z","content_changed_at":"2023-08-11T10:25:06.045000Z","complete_changed_at":"2023-08-11T10:25:06.047000Z"},{"id":"01h7j21pzpwccrn0qaekedqwah","content":"test2","is_completed":false,"created_at":"2023-08-11T10:25:06.038000Z","content_changed_at":"2023-08-11T10:25:06.041000Z","complete_changed_at":"2023-08-11T10:25:06.042000Z"}],"links":{"first":null,"last":null,"prev":null,"next":"http:\/\/localhost\/api\/tasks?cursor=eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ"},"meta":{"path":"http:\/\/localhost\/api\/tasks","per_page":10,"next_cursor":"eyJpZCI6IjAxaDdqMjFwenB3Y2NybjBxYWVrZWRxd2FoIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null}})
1 scenario, 0 skipped, 1 failure
ありがとうございますー
手元で再現したので原因を確認します!
http:\/\/localhost\/api\/tasks
の \/
が原因っぽいです。
runn側でうまく生成orパースの修正で対応ができないか確認します。
![togana](https://res.cloudinary.com/zenn/image/fetch/s--wXhC00wu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0e626620cc.jpeg)
確認ありがとうございます。
よろしくお願いします!
v0.80.3で修正してみました!もう一度 runn new
コマンドで生成してもらえると!
![togana](https://res.cloudinary.com/zenn/image/fetch/s--wXhC00wu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/0e626620cc.jpeg)
ありがとうございます!
実行できるようになりました!
以前と runn new
の生成するコードが変わり \/
は /
として生成されました。
![nitta](https://res.cloudinary.com/zenn/image/fetch/s--4b-aqKCl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a719916ff5.jpeg)
こんにちは。runnクックブックに載っていたコマンドを実行したところ以下の様なエラーが発生しました。原因がわからないので、教えていただきたいです🙏
$ uname -m
arm64
$ runn -v
runn version 0.91.0
$ curl https://httpbin.org/json -H "accept: application/json"
{
"slideshow": {
"author": "Yours Truly",
"date": "date of publication",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
],
"title": "Sample Slide Show"
}
}
$ runn new --and-run -- curl https://httpbin.org/json -H "accept: application/json"
Error: failed to load runbook /var/folders/7p/g09rrr4n5gjf6c9fwpp5bhjh0000gn/T/runn1459072591/new.yml: scope error: reading files in the parent directory is not allowed. 'read:parent' scope is required: /var/folders/7p/g09rrr4n5gjf6c9fwpp5bhjh0000gn/T/runn1459072591/new.yml
お、これはバグですね!
修正を進めますー少々お待ちください
https://github.com/k1LoW/runn/releases/tag/v0.91.1 で修正をしました。
ご確認ください!
報告ありがとうございますー!!!
![nitta](https://res.cloudinary.com/zenn/image/fetch/s--4b-aqKCl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/a719916ff5.jpeg)
修正確認できました🙇 ご対応ありがとうございます!💪
![goemon](https://res.cloudinary.com/zenn/image/fetch/s--wUEl9BVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50743544f9.jpeg)
runn チュートリアルに記載の内容で include を利用したシナリオを実行したところ、実際の出力が記載の内容と異なっていました。こちらは仕様変更によるものでしょうか?
$ runn --version
runn version 0.99.2
$ USER=katzumi runn run day12/**/*.yml --verbose
=== 既存のシナリオから新しいシナリオを作成する (day12/include.yml)
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
--- 1番目の記事の詳細を取得します (showFirstArticle) ... ok
--- 2番目の記事の詳細を取得します (showSecondArticle) ... ok
=== 単体のシナリオとして定義 (day12/list-articles.yml)
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
2 scenarios, 0 skipped, 0 failures
お、確認してみますね。おそらくバグな気がします。
バグ修正しました。報告ありがとうございました!
(なお ===
の行に ok
がないのは仕様変更です)
~/src/github.com/k2tzumi/runn-tutorial (main)> runn --version
runn version 0.99.3
~/src/github.com/k2tzumi/runn-tutorial (main)> env USER=katzumi runn run day12/**/*.yml --verbose
=== 既存のシナリオから新しいシナリオを作成する (day12/include.yml)
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
=== 単体のシナリオとして定義 (day12/list-articles.yml)
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
--- 1番目の記事の詳細を取得します (showFirstArticle) ... ok
--- 2番目の記事の詳細を取得します (showSecondArticle) ... ok
=== 単体のシナリオとして定義 (day12/list-articles.yml)
--- 指定された件数分、記事一覧を取得します (listArticles) ... ok
2 scenarios, 0 skipped, 0 failures
~/src/github.com/k2tzumi/runn-tutorial (main)>
![goemon](https://res.cloudinary.com/zenn/image/fetch/s--wUEl9BVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50743544f9.jpeg)
バグだったんですね。対応いただきありがとうございます!
runn の test について質問です。
APIのレスポンスが下記のようなjsonの配列形式である場合、type==firstのvalueが1であることをテストしたいです。test: current.res.body.actions[0].value == 1
としたりcompareを利用しても配列の順番が毎回変わる場合、actions[0]のtypeがfirstとは限らないためテストができません。
typeがfirstであることをfilterしてvalueが1であることをテストするなどなんらかの形でこのパターンのテストをしたいのですが難しいでしょうか?
{
"actions": [
{
"type": "first",
"value": 1
},
{
"type": "second",
"value": 2
},
{
"type": "third",
"value": 3
}
]
}
desc: Example
vars:
actions: [
{
"type": "first",
"value": 1
},
{
"type": "second",
"value": 2
},
{
"type": "third",
"value": 3
}
]
steps:
-
desc: test always second
test: 'find(vars.actions, {.type == "second"}).value == 2'
こんな感じですかねー。
実行できるランブックは以下です。
$ runn run gist://bbe1bf4c0d66aeb44304f60a57713f00#file-test-yml --scopes read:remote
.
1 scenario, 0 skipped, 0 failures
READMEにもあるようにrunnは https://expr-lang.org/ を内蔵していますのでみてみると良さそうです!
早速ありがとうございます、解決できました!
こんにちは!
runnを便利に使わせていただいております 🙇 ありがとうございます!
includeを利用してrunbookを書いた時の runn run runbook.yml --verbose
の挙動についての質問です。
test.yml
desc: "desc test when another runbook included"
steps:
dumpHoge:
desc: "dump hoge"
include:
path: include.yml
vars:
string: hoge
include.yml(include用のrunbook)
desc: "include runbook"
if: included
vars:
string: default
steps:
dumpString:
desc: "dump specified string"
dump: vars.string
サンプルとして、上記のようにtest.ymlからinclude.ymlをincludeを利用して呼び出して実行しているようなrunbookを記述しました。
このとき、runn run test.yml --verbose
を実行した結果が以下になるのですが、include側のステップに定義したdescription(include.yml)で呼び出し元のステップのdescription(test.yml)が上書きされている?ような出力になっている気がします。(想定通りの挙動だったらすいません 🙇)
実行結果:
$ runn --version
runn version 0.109.0
$ runn run test.yml --verbose
=== desc test when another runbook included (test.yml)
hoge
--- dump specified string (dumpString) ... ok <- ここが上書きされてる?
=== include runbook (include.yml)
--- dump specified string (dumpString) ... ok
1 scenario, 0 skipped, 0 failures
私は以下のような出力となると考えていたので質問させていただきました。
$ runn run test.yml --verbose
=== desc test when runbook included (test.yml)
hoge
--- dump hoge (dumpHoge) ... ok <- test.ymlのステップのdesc
=== include runbook (include.yml)
--- dump specified string (dumpString) ... ok
1 scenario, 0 skipped, 0 failures
お、これはバグかもしれないですね。確認してみます!
これはバグでした。修正していきます。
v0.110.0 で修正しました!
対応ありがとうございました!!!助かります!!!
![髙野 将](https://res.cloudinary.com/zenn/image/fetch/s--_cyogjP3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/785a0262aa.jpeg)
application/jsonレスポンスのbodyに"_"始まりのキーがあると、step.res.bodyにマップされずnullになる
でお知らせした件です。
再現手順
desc: アンスコをキーに持つJSONのテスト
runners:
req: https://gist.githubusercontent.com/
steps:
- req:
/masaru-b-cl/71f9e92d3a47504e78642e79fd016235/raw/f0f248e49a800b2c992086f67f3f985b5db73a9a/test.json:
get:
body:
application/json: null
test: |
current.res.status == 200
- dump: steps[0].res.body
- dump: steps[0].res.rawBody
$ runn run underscore-test.yaml --verbose
=== アンスコをキーに持つJSONのテスト (underscore-test.yaml)
--- (0) ... ok
null
--- (1) ... ok
{
"_hoge": "piyo"
}
--- (2) ... ok
1 scenario, 0 skipped, 0 failures
取得先JSON
{
"_hoge": "piyo"
}
期待結果
bodyにマップされ、test
コマンドで参照できる
備考
- Go言語 or runnの仕様上難しい?
- Go言語仕様上mapのキーは"_"許可されているらしいけど……
- それをrunnで
current.res.body._key
みたいに参照させる?-
current.res.body[_key]
みたいになっても多少は仕方ない気もするが
-
なるほど、アンダースコア…ちょっといい感じにするべく試行錯誤してみます!
こちらの再現データについてはレスポンスが Content-Type: text/plain; charset=utf-8
であることが原因でした。
$ go run cmd/runn/main.go run tmp.yml --debug
Run "req" on "アンスコをキーに持つJSONのテスト".steps[0]
-----START HTTP REQUEST-----
GET /masaru-b-cl/71f9e92d3a47504e78642e79fd016235/raw/f0f248e49a800b2c992086f67f3f985b5db73a9a/test.json HTTP/1.1
Host: gist.githubusercontent.com
Content-Type: application/json
-----END HTTP REQUEST-----
-----START HTTP RESPONSE-----
HTTP/2.0 200 OK
Content-Length: 21
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Cache-Control: max-age=300
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
Content-Type: text/plain; charset=utf-8
Cross-Origin-Resource-Policy: cross-origin
Date: Thu, 30 May 2024 12:56:41 GMT
Etag: W/"fe9ae6a411fbb6cad1c24ea0adef516862396cfa4bbf3c7ee20f6d58e1221a35"
Expires: Thu, 30 May 2024 13:01:41 GMT
Source-Age: 190
Strict-Transport-Security: max-age=31536000
Vary: Authorization,Accept-Encoding,Origin
Via: 1.1 varnish
X-Cache: HIT
X-Cache-Hits: 1
X-Content-Type-Options: nosniff
X-Fastly-Request-Id: 805660a4ab04e3973c0c6d7ac3934284565ca6d7
X-Frame-Options: deny
X-Github-Request-Id: A8C8:3EC055:929D4C:AB6F8F:665876C9
X-Served-By: cache-itm1220057-ITM
X-Timer: S1717073801.101739,VS0,VE1
X-Xss-Protection: 1; mode=block
{
"_hoge": "piyo"
}
-----END HTTP RESPONSE-----
Run "test" on "アンスコをキーに持つJSONのテスト".steps[0]
Run "dump" on "アンスコをキーに持つJSONのテスト".steps[1]
null
Run "dump" on "アンスコをキーに持つJSONのテスト".steps[2]
{
"_hoge": "piyo"
}
.
1 scenario, 0 skipped, 0 failures
$
強引に条件分岐を変えたところ current.res.body._hoge
は取得できるようでした。
~/src/github.com/k1LoW/runn (main)> git diff
diff --git a/http.go b/http.go
index b585969..a0206e3 100644
--- a/http.go
+++ b/http.go
@@ -528,7 +528,7 @@ func (rnr *httpRunner) run(ctx context.Context, r *httpRequest, s *step) error {
d := map[string]any{}
d[httpStoreStatusKey] = res.StatusCode
- if strings.Contains(res.Header.Get("Content-Type"), "json") && len(resBody) > 0 {
+ if strings.Contains(res.Header.Get("Content-Type"), "text") && len(resBody) > 0 {
var b any
if err := json.Unmarshal(resBody, &b); err != nil {
return err
~/src/github.com/k1LoW/runn (main)> cat tmp.yml
desc: アンスコをキーに持つJSONのテスト
runners:
req: https://gist.githubusercontent.com/
steps:
- req:
/masaru-b-cl/71f9e92d3a47504e78642e79fd016235/raw/f0f248e49a800b2c992086f67f3f985b5db73a9a/test.json:
get:
body:
application/json: null
test: |
current.res.status == 200
- dump: steps[0].res.body._hoge
- dump: steps[0].res.rawBody
~/src/github.com/k1LoW/runn (main)> go run cmd/runn/main.go run tmp.yml --debug
Run "req" on "アンスコをキーに持つJSONのテスト".steps[0]
-----START HTTP REQUEST-----
GET /masaru-b-cl/71f9e92d3a47504e78642e79fd016235/raw/f0f248e49a800b2c992086f67f3f985b5db73a9a/test.json HTTP/1.1
Host: gist.githubusercontent.com
Content-Type: application/json
-----END HTTP REQUEST-----
-----START HTTP RESPONSE-----
HTTP/2.0 200 OK
Content-Length: 21
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Cache-Control: max-age=300
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
Content-Type: text/plain; charset=utf-8
Cross-Origin-Resource-Policy: cross-origin
Date: Thu, 30 May 2024 12:59:40 GMT
Etag: W/"fe9ae6a411fbb6cad1c24ea0adef516862396cfa4bbf3c7ee20f6d58e1221a35"
Expires: Thu, 30 May 2024 13:04:40 GMT
Source-Age: 19
Strict-Transport-Security: max-age=31536000
Vary: Authorization,Accept-Encoding,Origin
Via: 1.1 varnish
X-Cache: HIT
X-Cache-Hits: 1
X-Content-Type-Options: nosniff
X-Fastly-Request-Id: 0bd36fd8af0cfdfa547293d2ce3b876985f62ffb
X-Frame-Options: deny
X-Github-Request-Id: A8C8:3EC055:929D4C:AB6F8F:665876C9
X-Served-By: cache-itm1220026-ITM
X-Timer: S1717073980.224917,VS0,VE1
X-Xss-Protection: 1; mode=block
{
"_hoge": "piyo"
}
-----END HTTP RESPONSE-----
Run "test" on "アンスコをキーに持つJSONのテスト".steps[0]
Run "dump" on "アンスコをキーに持つJSONのテスト".steps[1]
piyo
Run "dump" on "アンスコをキーに持つJSONのテスト".steps[2]
{
"_hoge": "piyo"
}
.
1 scenario, 0 skipped, 0 failures
~/src/github.com/k1LoW/runn (main)>
![髙野 将](https://res.cloudinary.com/zenn/image/fetch/s--_cyogjP3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/785a0262aa.jpeg)
確認ありがとうございます。
こちらでも runn を git clone
して確認して、 Content-Type
が "text/plain; charset=utf-8"であることを確認しました。
ということで Content-Type
で分岐していることがわかったので、元のHTTPストリーミングでレスポンスを返すAPIのレスポンスヘッダを確認したところ Content-Type: application/json
がありませんでした。結果的に step.res.body
がnullになっていることがわかりました。
一旦原因はわかりましたので、本スレッドはクローズとします。
また、調査、対応いただきたいことが出てきたら改めてスレッドを作成します。
ご対応、ありがとうございました。
![髙野 将](https://res.cloudinary.com/zenn/image/fetch/s--_cyogjP3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/785a0262aa.jpeg)
社内のエンジニアに共有したところ、 Content-Type: application/json
を付与する形に修正して解決しそうです
![髙野 将](https://res.cloudinary.com/zenn/image/fetch/s--_cyogjP3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/785a0262aa.jpeg)
無事解決しました!
runnのコードを私も読んで学びになりました。
今後変な動きっぽいのあったら、自分でrunnのGoコードを動かして試せそうです。
当初(結果的には)誤った情報で混乱させてしまい、お手数をおかけしました。
ありがとうございます!!!
![sadano](https://res.cloudinary.com/zenn/image/fetch/s--q2LrOHAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/37bbee2316.jpeg)
シナリオテストの作成に runn を利用させて頂いています。ありがとうございます。
include 利用時の vars のパス解決に関して質問させて下さい。
今作成中のシナリオでは、以下のように、include 専用シナリオに Go Template を使ってリクエストボディを指定しています。
steps:
generateCode:
bind:
resource_code: faker.UUID()
# リソース作成
createResource:
include:
# API を呼び出す include 専用シナリオを読み込む
path:
../../includes/create_resource.yaml
# API のリクエストボディは Go Template で指定
vars:
request: “json://../../scenarios/case1/request.json.template”
bind:
request: current.request
この際、include セクションでは vars を解決する際に path で指定したディレクトリを root とする為、vars には include 先からの相対パスで指定する必要がある認識です。
出来ればシナリオファイル間で vars の値を統一したく、これを、シナリオファイル自体からの相対パス(json://request.json.template
)で指定したいのですが、現状何か回避策はございますでしょうか?
現状パスは文字列として渡されるので「ルートシナリオファイルからの相対パス」にする方法はなさそうです。
可能であれば json://request.json.template
をルートシナリオで展開し、その vars を include ランナーに渡せればいいのですが難しそうですね。
![sadano](https://res.cloudinary.com/zenn/image/fetch/s--q2LrOHAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/37bbee2316.jpeg)
ご回答ありがとうございます 🙇
現状では回避が難しい旨、承知しました。
確かに先にリクエスト内容が展開できればそれでも良さそうですね。
また、これは思いつきなのですが、下記の bookWithStore で include 対象のパスだけ渡しているところを、include 対象のパスとは別に operatorRoot のパスを指定可能にするというのはどうでしょうか?
そうすると、例えば以下のような感じで書けるようになりますし、未指定の場合は path の値を使うようにすれば後方互換性も保てるかなと思います。
(ただ、include の vars 以外の処理への影響はあまり考慮できていないです。)
include:
root: ./
path: ../../includes/create_resource.yaml
vars:
# root で指定したパスで解決するので、ルートシナリオからの相対パスで記述可能
request: “json://request.json.template”
fmfm docker/build-push-action の context みたいな立ち位置ですね。
良いアプローチのように見えますが、一方で sadano さんのおっしゃるように影響範囲の特定が難しいですね。。。(runnは各シナリオファイルの位置をrootにすることで、どこで実行しても、include元のファイルを直接実行しても変わらないようにするという方針をとっています)
別のアプローチとして、例えば、 built-in function に os.Getwd の結果を受け取れる関数を用意するというのはいかがでしょう?
そうすると、シンタックスを増やすことなく、ある程度(あくまである程度ですが)の対応ができるかと思いました。
これもジャストアイデアなので、うまい解決ができると良いと思っています。
![sadano](https://res.cloudinary.com/zenn/image/fetch/s--q2LrOHAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/37bbee2316.jpeg)
runnは各シナリオファイルの位置をrootにすることで、どこで実行しても、include元のファイルを直接実行しても変わらないようにするという方針をとっています
なるほど。確かにこの方針は変えない方が安全ですね。
下手に入れると今後の機能拡張に影を落としそうな気がします。
別のアプローチとして、例えば、 built-in function に os.Getwd の結果を受け取れる関数を用意するというのはいかがでしょう?
汎用性があって、影響もユーザが意識的に使った範囲に限定されるので、良いアイデアだと思いました!
今回議題に挙げている jsonEvaluator についても、os.Getwd を使って絶対パスで指定すれば問題が解消できそうです。
ああ、${PWD}
と使うことで特に関数を提供することなく実現できそうですね...いかがでしょう?
macOS
$ echo $PWD
/Users/k1low
$
Linux
$ docker container run -it --rm --name test ubuntu:latest /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
eed1663d2238: Pull complete
Digest: sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30
Status: Downloaded newer image for ubuntu:latest
root@94c1abcf645c:/# echo $PWD
/
root@94c1abcf645c:/#
![sadano](https://res.cloudinary.com/zenn/image/fetch/s--q2LrOHAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/37bbee2316.jpeg)
なるほど、そういう手もあるんですね!
早速 $PWD
を変数に bind して以下のように書いてみました。
vars:
request: “json://{{ vars.pwd }}/request.json.template”
ただ、試してみたところ $PWD
は実行時のカレントディレクトリになるため、以下のようにテンプレートまでのパスを書かないとダメでした。
相対パスよりはいくぶん綺麗に見えますが、実行時のカレントディレクトリに依存するのも微妙な気がしたので、まだ相対パスの方が良さそうです。
vars:
request: “json://{{ vars.pwd }}/test/scenarios/case1/request.json.template”
絶対パスを使う方法で解決しようとすると、ルートシナリオファイル自体の絶対パスが取得できないと難しそうですね。
絶対パスを使う方法で解決しようとすると、ルートシナリオファイル自体の絶対パスが取得できないと難しそう
確かにですねえ。。
![sadano](https://res.cloudinary.com/zenn/image/fetch/s--q2LrOHAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/37bbee2316.jpeg)
ただ、現状の相対パスでの記述でも問題なくテスト実装出来ており、非常に便利に利用させて頂いています!
何か妙案が出て解消できそうであれば、是非検討して頂けるとありがたいです 🙇
ありがとうございます!何か考えます!