-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
サクラエディタの起動テストを追加する #1385
サクラエディタの起動テストを追加する #1385
Conversation
1. tests1.exeをサクラエディタとして起動できるようにする 2. tests1.exeを使ったテストで影響が出る箇所を修正する 3. WinMainTest - runWithNoWin追加(コントロールプロセスの起動テスト) 4. WinMainTest - runEditorProcess追加(エディタプロセスの起動テスト)
✅ Build sakura 1.0.3047 completed (commit d3de83ae55 by @berryzplus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
気になる点としてPRのタイトルは「サクラエディタの起動テストを追加する」なのでテストの追加を行うPRに見えますが、sakura_core/_main/CProcessFactory.cpp
ファイルも変更しているので、実際にはテストの追加だけではないです。
既存のコードに動作上の問題があるので変更しているのでしょうか?動作上の問題は無いけれどテストをしやすくする為に変更しているのでしょうか?テストをしやすくする為だけの変更であれば、個人的な意見に過ぎませんがメリットを実感しにくいです。
あと変更が有るとその内容に問題が無いか確認しないといけないですが、これはまぁPRのレビューする以上しょうがないですね…。
「仕様・動作説明」の2の変更のことを言ってると思います。 文字数多いので読むのたいへんだと思いますが、何故そうしてるかは書いてあるつもりです。 2の変更は、今回のPRの目的を達成するために必要だったので(≒起動テストを実現するにの不都合だったから)変更していますが、本質的には「もとの処理に不具合とまでは言えない軽微な誤りがあった」のを修正しています。 このPRは #1363 で作成したコードなので、元来は複数のコミットに分かれたものを結合して1コミットにして作成しています。コミットを分けたらどの修正行が 2 の変更にあたるか見やすくなるんですけど、そうすると「もとの処理に不具合とまでは言えない軽微な誤りがあったのを修正する」という独立した PR を作る衝動にかられる気がします。
修正の影響は「エディタ起動時にコントロールプロセスを起動するときの初期化完了待機」なので、 |
PRを分ける・分けないは別にして、コミットを分けたほうが分かりやすかったりしますかね? 目的が理解できるか?目的を容認できるか?目的を達成しているか?が判断できればレビューは可能だと思いますし、医術的な検証は #1363 で終わってるので、コミットくっ付けてしまったんですが内容を理解するために分けた方がいいってことならコミットを分けてもよいです。 |
今のタイトルや説明だとテスト追加がメインで2の変更が付随的なものに見えます。タイトルでテスト追加といいつつ本体の変更もついでにしてるのが何か違和感があるというか… 潜在バグが複数あるとしてそれへの対処をするならその修正にフォーカスしたPRの方が個人的にはわかりやすいです。 後から2の変更の説明に#1363のコメントへのリンクを張って済ませてますが、PRの説明でちゃんと書いた方が分かりやすいです。 課題に取り組んでる本人は圧縮率が高い記載で問題ないと感じても、意識を払ってない他人には分かりにくいという。。 まぁバカでも分かる解説書くなんてかったるくてやってらんねーよ、っていうのもそれはそれで全うな意見だと思います。 |
コミット分けますね。 |
This reverts commit 9da897d.
ゼロから説明しなおしたほうが良さそうなので一旦変更を revert します。 積み直しの1発目コミットは、起動テストの追加です。 もっとも、この方式にはいくつか問題があるため、色々対策が必要になって 9da897d が出来上がった経緯があります。 |
wWinMainを呼び出してエディタプロセスを起動するシンプルなテストを実装してみました。
❌ Build sakura 1.0.3098 failed (commit 8b8611e41e by @berryzplus) |
「不具合とまでは言えない軽微な誤り」を実演するのは重要ではなくて(#1363 を見れば何か問題が起きたらしいというのは分かりますから)、分析結果を示していただける方が理解が進むと思います。
通常使用でも問題が発生しうるのであれば、確かに本体を直した方がよいだろうということになるでしょうし、通常使用では絶対に発生しないのであれば、テスト方法がよくないのではという疑問が出てきます。 |
サクラエディタのプロセスには、エディタプロセスとコントロールプロセスがあります。 サクラエディタのコードの大半は、コントロールプロセスがINIファイルから作成した「共有メモリ」というプロセス空間にまたがって存在するグローバルデータに依存しています。このため、サクラエディタの実行中は基本的に、コントロールプロセスが実行されている必要があります。 エディタプロセスを起動しようとするとき、コントロールプロセスが起動されていない場合にはコントロールプロセスが起動されます。コントロールプロセスを起動しただけでは意味がないので、起動したコントロールプロセスが「共有メモリ」を作成してグローバルデータが使用可能になるまで待つようになっています。 「不具合とまでは言えない軽微な誤り」は、ここの待機のやり方(=待機完了を検知するためのトリガー)がおかしいことを指しています。 誤りの影響は通常使用でも条件により発生するタイプで、実害は「エディタの起動が遅い」です。 エディタプロセスの開始時にコントロールプロセスが起動されていない場合のみに発生する事象ですが、人によっては毎朝体験している不具合かも知れません。発生メカニズムはあとで解説します。 |
977a210 のビルドでは、エディタプロセスの起動処理内でコントロールプロセスを起動しようとするコードが自分自身(=
テスト結果が成功と失敗に分かれている理由は、起動結果を検知していないことが原因です。 対策としては、 |
❌ Build sakura 1.0.3099 failed (commit 9b5ccf2689 by @berryzplus) |
なんとなく、問題とされてる部分はテストじゃないように思うので、このへんの課題解決の過程は割愛してしまいます。 次のコミットでサクラエディタ本体に加える変更以外を元に戻してしまいます。 |
✅ Build sakura 1.0.3100 completed (commit 5366877491 by @berryzplus) |
コントロールプロセスの初期化に掛かった時間をテストで取得できるようにすれば、このPRの本体側の変更の効果が見えやすいです。 PRの説明やコメントで速くなったよ!!といわれて多分そうなんだろうとは思いますが…。 あとその本体側の変更を入れないとプログラムを起動して終了するテストが書けなくて成立しないなら、その理由、メカニズムをPRの説明に書いた方がいいです。 変更入れなくても成立するけど、コード見てて非効率な処理してて気になったからテストのついでに直しとく、という事ならそれは別PRにした方がいいと思います。 |
ビルド成功してしまってるので、問題点が超わかりづらいっすね・・・。 これ実は、エディタプロセスを起動するテストなのにエディタプロセスを起動できていないんです。 何故そうなるかというと、エディタプロセスが正常に起動できなくてもエディタは正常終了するからで、エディタプロセスが正常に起動できたかどうかを知る方法は存在しないからエディタプロセスを正常に起動できたかどうかをチェックしていないからです。 理由として挙げた問題点は、華麗にスルーします。
|
実演する必要はないと言っています。問題点をどう分析したかを示してください。
他のパターンはとりあえず置いておきましょう。今回のパターンにおいて、なぜ起動できないのか、どう修正することで起動できるようになるのかを示してください。 と、ここまで書いて今さらながら思ったのですが、ここでやろうとしているテスト(システムテストの一部と見做すべきでしょうか)は単体テストではありません。テストすべきはユーザーが実際に使う |
測定しても無駄っす。 現象は「起動が遅れる」なんですけど「どれくらい遅れるか」が全く読めないからです。 全く読めない、の理由は、変更前が WaitForInputIdle だからです。 WaitForInputIdle は、待機対象のプロセスが開始したメッセージループが「開始後に初めて空振ったタイミング」を検出する関数です。この関数を使うと、他の人が書いた内部仕様が良く分からないプログラムを起動して待機するコードが簡単に書けます。待機する対象がGUIプログラムであれば表示したメインウインドウが初期表示のメッセージを食い尽くしてアイドル状態になる瞬間は必ず存在するので、有効な待機手段の一つだと思います。 ただし、より高度な待機機構であるイベントやミューテックスを利用できるようなら、そちらのほうが信頼性は高いです。コントロールプロセスのメッセージループが初めて空振るタイミングは、待機すべき処理(=共有メモリの初期化)が完了するタイミングよりもだいぶ遅いので、ピンポイントで同期を発動できる同期オブジェクトを使うほうが有効です。 ちなみに WaitForInputIdle は 非GUI プロセスには使えません。 なので、コンソールプログラムである |
当然ですが、分かっててやってる愚行です。 探しているのは、強引でも「単体テスト」を実現する方法であって、 サクラエディタのグローバル依存はぼちぼち深刻です。 そいつらを現代的なコードに組み替える方法は2つしかないです。 テストするのを諦めるって方法は、初期にやって失敗してます。 代替策があればそれに乗っかる感じです。 |
何のためにテストをするのか、テストの目的をはっきりさせましょう。PR の目的には
と書いてありますが、これを目的とするならば単体テストの範疇ではないですし、
何のために単体テストを実行しますか? |
const auto pszProfileName = CCommandLine::getInstance()->GetProfileName(); | ||
std::wstring strInitEvent = GSTR_EVENT_SAKURA_CP_INITIALIZED; | ||
strInitEvent += pszProfileName; | ||
HANDLE hEvent; | ||
hEvent = ::OpenEvent( EVENT_ALL_ACCESS, FALSE, strInitEvent.c_str() ); | ||
hEvent = ::CreateEventW( NULL, TRUE, FALSE, strInitEvent.c_str() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これが修正のメインです。問題点の3つ目です。
動いているので「不具合とまでは言えない」ですが、やり方が間違っています。
このやり方を成立させるには、このコードが実行される前に、起動されたプロセスがCreateEventを完了している必要があります。イベント発動を待機するにあたり、イベント発動するスレッドがイベントを開いていることは必要条件ではないので、ノーチェックで CreateEvent してしまえばよいです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これは問題無さそうですね。
if( !IsExistControlProcess() ){ | ||
// コントロールプロセスが多重起動防止用のミューテックス作成前に異常終了した場合など | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
問題点の2つ目です。
このコードは「コントロールプロセスを起動した」ときにだけ呼ばれるコードです。
IsEistControlProcessで確認するのは「コントロールプロセスが既に起動されているかどうか」なので、ちょっと違うチェックをしています。エディタプロセス的にはコントロールプロセスが排他制御ミューテックスを作成しているかどうかに気にしなくてよいことです。(後続をOpenEventするためだけに必要。)
// 唯一生き残ったコントロールプロセスが多重起動防止用ミューテックスを作成しているはず。 | ||
// | ||
int nResult; | ||
nResult = ::WaitForInputIdle( p.hProcess, 10000 ); // 最大10秒間待つ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
問題点の1つ目です。
WaitForInputIdleはプロセス同期を簡易化する目的で用意された便利関数なので機能に制限があります。
制限
- 対象が GUIプロセス でなければ利用できない。
- 対象が マルチスレッド である場合の挙動が保証されない。
- アイドル検出のタイミングを制御できない。
より高度な同期オブジェクトを使える場合にはそちらを使うべきで、ここのコードは既に同期オブジェクト(イベントオブジェクト)を利用しています。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コントロールプロセスの初期化完了を後の処理でイベントを使って確認しているのでこのチェックは不要だという事ですね。
このチェックが有っても害は無さそうだけど、テストする都合上削りたい的な事なんじゃないかと思います。
WaitForInputIdle
ってプロセスがユーザー入力を受け付けることができるアイドル状態になっているかを確認するAPIのようですが、コントロールプロセスが GUI を持つかというとタスクトレイのウィンドウを作ってメッセージループを回すので該当すると思います。
さてこのチェックが無くても本当に問題が無いのか?ですが、まぁ多分大丈夫なんじゃないでしょうか…。コントロールプロセスとエディタプロセス間でユーザー入力による待ち受けとかは無いので。
✅ Build sakura 1.0.3101 completed (commit 122e18c478 by @berryzplus) |
とりあえず、だいぶ説明を足してみました。
コンソールアプリとして起動したプロセスを、GUIプロセスと誤認させる手法があれば本体の修正は不要です。 |
「不具合とまでは言えない誤り」を修正しなければならない理由は分かってもらえたと思いますがどうでしょう? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
起動プロセス周りの変更は多分問題無いと思います。
テストコードですが tests1
プロジェクトのビルドで
1>ninja : error : rebuilding 'build.ninja': subcommand failed
というエラーメッセージが出てビルドに失敗したので他の人に任せます。
@berryzplus さん
色々な解説のコメント書いていただいて理解しやすくなって助かりました。ありがとうございます。 自分の事は棚を上げてPRの説明に関して思う事を書きますが、これこれこういう変更をしたよの記述の後にその理由の解説を別チケットのコメントのリンクで済ますのは、あまり良くない気がします。PR単品で説明が完結していなくても問題が無いとなると、極端な事を言うとコメントのリンクを延々と辿ってからやっと解説に辿り付くようなものも有りえてしまうような…。 とはいえ物事の理解って1ページの文章で済ませられる事ばかりでは無いと思いますし、文章の書き方には多彩なアプローチがあると思うので、別にそのやり方が良くないと言いたいわけではありません。 |
前の段落で |
cmakeはconfigオプションを変えてリビルドする使い方に弱いみたいです。 一度 コミットしてないファイルは消えてしまうので注意して下さい。 |
このPRの変更内容が原因ではなく元からの挙動のようですが、サクラエディタ起動後にコントロールプロセスをタスクマネージャーで終了してから、残っているエディタプロセスで コントロールプロセスを強制終了させる不正操作がイレギュラーで想定外なのでしょうがない気もしますが、そうなるメカニズムを追って調べてみました。
タスクマネージャーでタスクを終了させる操作はWindows8以降は 異常系でもリカバリする対策を入れるべきかというと、まぁあえて実装しなくても問題無いだろうなという気はします。 |
過去の生成物が残っていてテストの新しいビルドを行う上で不整合があったんですね。 対処方法を教えてくれて助かりました。ありがとうございます。 |
テストを実行して テストコードをざっと眺めてみましたが、 あと起動テストとして具体的に何を確認していきたいのかがPRの説明に書かれていないので分かっていません。
エディタが単純に起動するかどうかだけの話だったらPR作成者が事前にきちんとローカルでビルド後に実行すれば済む話だと思うので、それだけでは意義は無いと思います。エディタプロセス起動後に特定の処理や操作が問題無く行えるかの様々な確認が自動で行えるという事ならば、人がその都度労力を使って確認する手間を減らせるので省力化という事で意義があると思います。 そういえば過去にberryzplusさんがPR作成者で自分がPRのレビューアだった際に正常起動の確認さえもせずに master に merge 後に不具合の指摘が来た時がありましたがそれは単に両者の怠慢なので、単体テストで単純に起動するかの確認をしたいという話とは別ですね。 |
#1385 (comment) の件は、気付いていました。 対策としては CControlProcess の存在チェック方法や起動時の排他制御機構に手をいれなくちゃいかんくて、 たぶん、何のためにコントロールプロセスを起動するのか?という話と、 このPRで提案している、実際にコントロールプロセスを起動して待機してみるテストは、この問題を解決するためにも必要だと思っています。 |
この件の対策を #1384 で出しています。 |
本体側にある関数を直接利用していないのは、それらがprivateなメンバ関数であるために流用できないからです。 |
目的に書いているとおり、起動できているかどうかを確認しています。 runWithNoWinではコントロールプロセスを起動して初期化完了を待機してから、非表示のメインウインドウを閉じてプロセスの終了を指示し、プロセスが実際に終了するまで待機します。この間に何か問題が起きれば例外が発生してテストが失敗するようになっています。(例外時の終了コードは非ゼロ)。iniの存在チェックはおまけで、なくても良いかもしれません。 コメントに書いていますがエディタプロセスが起動できたかどうかを知る方法はないため、 runEditorProcess ではエディタプロセスが起動するであろう コントロールプロセスの初期化完了を待機してから、コントロールプロセスが実際に終了するまで待機します。iniの存在チェックはおまけで、なくても良いかもしれません。 |
手作業だと人によって
気を付けようね!というのとは別な話です。 |
#define private public を入れるのだと駄目なんでしょうか? |
手作業で確認するのは PRの内容とか出す手間にもよりますが、起動してちょっとした操作が出来るかとか記述を変更した箇所が期待通りに動作しているかの確認ぐらいはPR出した人もレビューアーも行うべきだし、あとPR出した人は手元の変更を commit した後に push する前にローカルできちんと確認を行うべきですね。そうしないとレビューアがバグ取りをする羽目になります。#631 (comment) とか…。 ソフト開発者はプログラムで自動処理させる事を頑張る必要があるので手を抜けるとこには手間を省きたがるのは理解出来ます。 |
おーさすが各所の理解が深いですね。サクラエディタのソースコードは行数が多い上に大勢の人が関わっていて、とか色々な言い訳を適当に書き連ねてしまいそうですが自分は理解してないとこがかなり多いです。
確かに手を入れている箇所が単純ではなかったり、人によっては興味が無い変更とかは食いつきが悪くてなかなかレビューがされないかもしれないですね。
前者は設計や仕様寄りの話で後者は必要な動作をどのようにして実現するかという実装寄りの話かもしれないですね。ところでコントロールプロセス無しで今の機能を実現出来ないかというとタスクトレイのウィンドウは別にしてなんか実現出来そうな気がぼんやりしました…。エディタプロセス1種類のみにしてあえて区別する作りにはしない方がもしかしたら良かったりするかも??タスクトレイのウィンドウに関しても最初に起動したエディタプロセスが担当して、他のエディタプロセスが存在する状態でエディタのウィンドウが閉じられた場合にはエディタプロセスを閉じずにタスクトレイのウィンドウの為にプロセスを落とさない動作させるとか…。まぁ妄想だけでなく実際に調査や実装して確認しないと結論出せないですね。
そうですねぇ、起動に関わる処理が動作している事を確認する手段の一つではあると思います。ただ成果物の動作確認方法としては、KENCHjpさんが #1301 (comment) で書かれてたソフトを使って、実際にユーザーが使う ところでプロセスやスレッド間の連携が隙が無く動作するかを頭の中で検証したり動作確認するのはややこしいのでシーケンス図やステートチャート図などがあると良いなと思います。まぁコードを見て理解しようとしない甘えかもしれませんが。 |
説明ありがとうございました。具体的にやっている事までをPRの説明に書いてほしいとか言い出すと、テストのソースコードまるまるコメントに貼り付けされても文句は言えないですね。 エディタプロセスが起動出来たかをどうかを知る方法はこのPRの変更で削られた
このPRでやっている事はサクラエディタ自体の起動テストではなくプロセス起動処理をテストに組み込んでの動作なので、そのアプローチの延長線でテストを増やしていくと、テストの都合でまた本体側を改造する羽目になりそうな気はします。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
テスト手法が気になりますがまぁ動いているので良いんじゃないでしょうか。
それでよい場合もあると思います。 短時間で元に戻せる確証があるときは、privateをpublicにしちゃうこともあります。 今回はすぐに戻せないんで、また、全体的に間違ってるわけではないと思うのでやらなかったつもりです。 |
最終的には1種類だけにするのが分かりやすいと思うんですけどね。 2つ以上のプロセスを協調させる設計は、 エディタウインドウを管理する、というタスクを完全にエディタ側に委譲できれば実現可能と思っています。 |
WaitForInputIdleの制約事項に「待機対象がマルチスレッドだと使えない」があります。 検索や描画のためのドキュメント解析をマルチスレッド化したい思惑があるので、それはもう使わない方向になると思います。利用箇所がいくつか残っているんですが、それらは「エディタプロセスの起動を検知できるようにする」を提案するときに置き換えるんじゃないかな?と思っています。
「テストの都合で本体側を変えたい」と言いそうな候補として「Grepダイアログ」があります。 |
レビューありがとうございます。マージしちゃいます。 |
PR の目的
サクラエディタに起動テストを追加することにより、プログラム起動の成否に関わる不具合の混入を防止します。
単純にプログラムを起動して終了するテストを指しています。
カテゴリ
PR の背景
#1363
スモークテスト導入に至るまでにある障害事項を提示する
で書いている以下の問題の対策です。#1363 で作成したコードを吟味して1コミットにまとめてPRする次第です。
導入されると、少なくとも「プログラムの起動ができない」という系統の不具合をPR時点で検出できるようになります。
PR のメリット
PR のデメリット (トレードオフとかあれば)
仕様・動作説明
サクラエディタ自体の仕様はほとんど変わりません。
main関数を実装してtests1.exeの起動オプションを追加します。
googletestのオプションと衝突させないため
-PROF=
が含まれる場合だけwWinMain
を呼ぶようにします。起動したコントロールプロセスの初期化完了を待機する処理を変更します。
GUIプロセス専用関数である
WaitForInputIdle
を使うのをやめます。スモークテスト導入に至るまでにある障害事項を提示する #1363 (comment)コントロールプロセスを起動した直後に
CreateMutex
して存在確認していたのをやめます。スモークテスト導入に至るまでにある障害事項を提示する #1363 (comment)コントロールプロセスの初期化完了待ちのためのイベントをOpenしてたのをCreateするように変えます。スモークテスト導入に至るまでにある障害事項を提示する #1363 (comment)
初期化完了イベントが取れなかったときに無視して続行していたのをやめます。
コントロールプロセスを起動して、終了するテストです。
エディタプロセスを起動して、終了するテストです。
テスト内容
追加したテストが PASS になればOKです。
PR の影響範囲
コントロールプロセスが存在しない状態でエディタプロセスを起動しようとした場合の、
エディタプロセスが、起動したコントロールプロセスの初期化完了を待機する方法が変更されます。
関連 issue, PR
#1301
スモークテストを導入したい
#1363
スモークテスト導入に至るまでにある障害事項を提示する
参考資料