単体テスト(インデックスと検索)¶
扱うこと: 共通基盤・設定ファイル・インデックス名と TSV の名前・インデックスの管理・本文インデックスの単体テストが何を確かめるか。扱わないこと: インデックス作成そのもののテスト(単体テスト(インデックス作成))、Office のテスト(単体テスト(Office))、画面のテスト(画面のテストと確認)。先に読むページ: テスト。
flowchart LR
core["tests/shared/core/<br>共通基盤"] --> settings["tests/tebunko/core/<br>設定ファイル"]
settings --> name["tests/tebunko/index/index_name<br>インデックス名"]
name --> store["tests/tebunko/index/index_store<br>インデックスの管理"]
store --> contentIndex["tests/tebunko/index/content_index_format<br>tests/tebunko/search/content_index_search<br>本文インデックス"]
共通基盤(tests/shared/core/)
| 対象 | 主な確認内容 |
|---|---|
toSafeFileName・getFolderNameProblemText・testPlainFolderName(text.Tests.ps1。ファイルに触らない単体テスト) |
禁止文字の全角化、/ → /、" → ”、使用可能文字は不変。フォルダ名の確かめ(空・前後の空白・長すぎる・使えない文字・最後の .・予約名) |
testSourceBookName(index_name.Tests.ps1)・formatDisplayText(text.Tests.ps1) |
取り込み対象の拡張子(Office・テキスト・PDF)だけ真、.hta・.lnk・.exe・拡張子なし・区切りや ..・:・制御文字・向きを変える文字を含む名前・終わりの . や空白で拡張子を隠した名前は偽。表示用の文字列は制御文字・向きを変える文字を U+FFFD にし、タブ・改行は keepLineBreaks のときだけ残す |
toLongPath / fromLongPath・長いパス |
\\?\・\\?\UNC\ の付け外し、260 文字を超えるパスの読み書き |
copyFileShared |
ほかのアプリが書き込み用に開いているファイルもコピーでき、コピー中もほかのアプリの書き込みを妨げない |
invokeWithSourceCopy |
読む処理の戻り値・出力をそのまま返す、読む処理が失敗してもコピーと追加のファイルを消す、読めたのにコピーを消せないときは取り込みの失敗にする、元のファイルが無いときは読む処理を呼ばない |
readListFile / writeListFile |
[ ]・先頭の空白を含むパスの往復、ファイル無しは空配列、読めないファイルは例外、行が無ければ空のファイル |
writeTextLinesAtomic |
新しいファイルを作り、一時ファイルを残さない。既定は BOM 付き UTF-8、文字コードの引数で BOM なし UTF-8 にできる |
formatFileTime |
秒までの日時(yyyy/MM/dd HH:mm:ss) |
removeDirectoryRetry / removeTreeNoFollow |
中身ごと削除(読み取り専用・260 文字超も)、フォルダが無ければ何もしない、中のジャンクション・シンボリックリンクの先は消さずリンクだけを外す、消すフォルダそのものがリンクならリンクだけを外す(読み取り専用のジャンクション・末尾に \ が付いたパスも)、確かめられないフォルダが兄弟・根にあっても何も消さずに失敗する、数えたあとでフォルダがリンクに替わったらそのフォルダのファイルを消さずに失敗する、親を指す輪のジャンクションがあっても時間内に終わる(別スレッドで上限を決めて待つ)、予約名に拡張子の付いた名前(aux.backup・CON.txt)のフォルダも消える、消したあとにフォルダが残っていれば成功にせず決めた回数だけ試して例外にする、有無が分からないとき(届かない・読めない)は試し直さず例外にする、空のパスは何もしない(removeDirectoryRetry)。呼ぶ側(removeDroppedFolders・RemoveIndex・importIndex)は、有無が分からない例外のとき「削除しました」を出さず記録を残す、片付けの失敗が元の失敗の理由を隠さない |
getFilesNoFollow(リンク) |
下のファイル・フォルダを集める(末尾の \・Hidden の既定と -IncludeHidden)、ジャンクション・親を指す輪・ファイルのシンボリックリンクは降りず Links に入れる、root がジャンクションでも先の中を集める、リンクかどうか確かめられないフォルダは既定では降り -SkipUnknown では Links に入れる、読めないフォルダは HasError で返す |
getIndexExportSize / exportIndex(リンク) |
置き場の中のジャンクションの先のファイルを zip に入れず大きさにも数えない、親を指す輪があっても止まらない |
testLinkItem / getLinkKind |
普通のファイル・フォルダ、ReparsePoint のファイルで LinkType が空のもの(OneDrive)はリンクでない、ジャンクション・シンボリックリンクはリンク、フォルダで ReparsePoint があり LinkType が空・$null・知らない値のものと、属性が読めないものはリンクとして扱う(getLinkKind は Unknown。辿らず・消さずに失敗する) |
trimTrailingSeparator |
末尾の \・/(重なっていても)だけを落とす、途中の区切りは変えない、ドライブのルート・\\?\C:\・\\?\・\・/・空のパスはそのまま返す |
testPathIsLink / testPathHasLink |
リンクと普通のフォルダ・無いパス・空のパス、途中の階層のリンクを見つける、base 自身がリンクでも見ない、まだ無い階層から先は見ない、path が base の下になければ真 |
testRemovablePath |
根の下は真(まだ無い階層の下も)、根そのもの・根の外・似た名前・.. で外へ出るもの・相対パス・空は偽 |
removeDirectoryUnder / moveDirectoryUnder |
置き場の下のフォルダを消す・移して真、置き場の外・置き場そのものは何もせず偽、途中の階層がジャンクションなら(置き場の下に見えても)リンクの先を消さず移さず偽、消す・移すフォルダそのものがジャンクションなら(途中ではないため)リンクだけを外し先は消さない、置き場そのものがジャンクションの構成では下を今までどおり消せる、元と先で根が違うときは sourceRoot で渡す |
newDirectoryUnder |
根の下に途中の階層ごと作る(すでにあっても真)、途中がジャンクションなら何も作らず偽でリンクの先に書かない、根そのものがジャンクションでも作れる、根の下でない・似た名前・空のパスは偽、作ったあとにリンクに替わっていれば偽 |
invokeWithNamedMutex |
出力を返し手放す、例外でも手放す、同じスレッドの入れ子は通す、別のスレッドが持ったままなら時間切れの例外、持ったまま終わったスレッドの後は abandoned として続ける |
newAppMutex |
同じ処理・同じ配置フォルダでは 2 つ目を取得できない、処理の種類・配置フォルダが違えば同時に取得できる、名前は Local\<app>_<name>_<鍵>、app が空なら例外 |
formatTime |
空は空、当日は時刻だけ、前の日以前は月日つき |
replaceCellNewLine |
" 内の改行(LF・CR・CRLF)は U+2028 に置き換え、" 外の改行は保持 |
formatTsv |
行末の空セル・末尾の空行の除去(途中の空行は保持)、セル内改行を含む行を 1 行にまとめる、使用範囲の左上に合わせた先頭の空行・空セルの補完、空白だけなら空 |
prettyTsv |
UTF-16 → UTF-8(BOM 付き)、[ ] を含む出力パス、使用範囲の左上の指定、空なら出力しない |
countTsvFields |
タブ区切りのセル数、先頭のセルが空の行、" で始まるセル内のタブ・改行は区切りとしない、途中の " は囲みとしない |
toColumnName |
列名(A Z AA ZZ AAA XFD) |
normalizeFolderPath |
前後の空白・引用符・末尾の \ の除去、ドライブ直下は D:\ のまま、UNC パス、/ → \、\\?\ ・ \\?\UNC\ の除去、重なった \ ・ . ・ .. の解決、環境変数の展開、相対パスは $rootDir から、解釈できないパス(* を含む・\\server ・ \ ひとつで始まる)は書かれたとおり |
getPathUnderFolder |
フォルダからの相対パス(フォルダ自身は空、末尾の \ ・大文字と小文字の違い、ドライブ直下、UNC の共有直下)、フォルダの下でなければ $null(フォルダ名の途中では一致しない) |
joinSourcePath |
ドライブ直下・相対フォルダが空の場合・共有フォルダ・空の名前 |
getFolderPathAliases / testSameFolder / testFolderUnder |
ネットワークドライブ ⇔ UNC パス、subst のドライブ ⇔ 割り当て元のフォルダ、ドライブ直下・共有直下、割り当ての無いパスは自身だけ、同じフォルダの判定(割り当ては差し替えてテストする)、この PC の割り当てでも例外にならない、入れ子のフォルダの判定(自身も「中」・名前の先頭が同じだけのフォルダは別・別の書き方でも分かる) |
getFolderLeafName |
ドライブ直下・UNC・末尾の \ |
フォルダ選択を開く場所(getExistingAncestorFolder) |
画面のテストと確認 |
設定ファイル(tests/tebunko/core/settings)
| 対象 | 主な確認内容 |
|---|---|
readSettings / writeSettings / updateSettings |
ファイル無しは既定値でファイルを作らない、保存と読み込みの往復(1 件だけの一覧も配列のまま)、1 つのキーだけ変えてもほかのキーを保つ、記載の無いキー・空のファイルは既定値、JSON として読めなければ例外 |
getTargetFolders / writeTargetFolders |
記載順、enabled が false はチェックなし・記載が無ければチェックあり、引用符・末尾の \ の除去、空のパスは除く、重複(大文字・小文字・末尾の \ の違い)は最初のものだけ、設定が無ければ空、保存と読み込みの往復(ほかの設定を保つ) |
readIndexSources / setIndexSourceFolder |
インデックス名に対する元のフォルダの記録(source_folder.txt)と読み込み、同じ名前は 1 か所(上書き)、クロール対象フォルダにある名前ならその path を書き換える(indexSources には入れない)、名前・フォルダが空なら何もしない |
readSearchExcludes / writeSearchExcludes |
無ければ空、保存したフォルダと直下だけの区別を読み戻す(末尾の \ を除く、空のパスは除く)、ほかの設定を変えない、空で保存すると空 |
インデックス名と TSV の名前(tests/tebunko/index/index_name)
| 対象 | 主な確認内容 |
|---|---|
encodeIndexPlace / decodeIndexPlace |
禁止文字・_・%・制御文字の %XX 化と往復、衝突" と 衝突” が別の名前になる、使用可能文字(全角記号・空白・&'#())は不変、符号化で作らない %XX(100% 等)は戻さない |
splitObjectPlace / describePlace / describeHitPlace |
図形・コメント・ヘッダー・フッターの場所の分解、場所ごとの表記(見出しの要約・検索結果ファイル)と種別の文字、行ごとの表記(表の「場所」列。セル番地・ほかの数・行番号) |
toIndexFileName / convertIndexFileNameToPlace |
決まった種類の場所は英語の固定名(ページ・スライド・見出し等)との往復、図形・コメント・ヘッダー・フッターの [shape] [comment] [header_footer]、決まった種類でない場所(Excel の任意のシート名)は符号化との往復、固定名と符号化がぶつからないこと、255 文字の上限 |
placeKindFileNames / newObjectPlaceKindPattern |
図形・コメント・ヘッダー・フッターの種類と objectPlacePattern がそろっていること、末尾の種類を当てる正規表現(全部・種類を絞ったとき・[埋め込み] [埋め込み01]・知らない種類) |
newIndexName / assignIndexNames |
フォルダ名・ドライブ名・共有名、重複時の (2) (3)、設定の名前をそのまま使う(フォルダの場所が変わっても同じ名前)、名前が無ければ前回の取り込み一覧の名前・フォルダ名から作る、設定にある名前はほかのフォルダに使わない、名前にできない・長いフォルダ名(切って末尾が .・空白になる場合と (2) を付ける場合も含め、作る名前は必ず testIndexName を通る) |
splitIndexRelPath |
先頭のインデックス名と残り(深い階層、数字を含む名前)、\ が無い場合 |
testStoredIndexName |
予約名(aux.backup・CON.txt・CON)は通す。空・前後の空白・使えない文字・最後の .・.. は testIndexName と同じく理由を返す |
testIndexName |
使える名前は空文字列、空・前後の空白・255 文字超・使えない文字・末尾の .・Windows の予約語(大文字小文字を区別しない)・ほかのインデックスと重複、それぞれの理由を返す |
インデックスの管理(tests/tebunko/index/index_store)
| 対象 | 主な確認内容 |
|---|---|
getIndexStats / testStatusRowFailed |
インデックス名ごとの件数(合計・済・未取り込み・失敗)と最終取り込み日時を集計する。未取り込み でエラーがある行(利用者の PowerPoint のために取り込めなかったファイル)は失敗に数え、待ちに数えない |
IndexCatalog.RenameIndex |
work\store\content\<旧名> を改名して中身をそのまま残し、取り込み一覧のクロール対象フォルダの行と各行の相対パスの先頭を書き換える。ほかのインデックスの記録は変えない。フォルダがまだ無くても記録は書き換える。同じ名前のフォルダが既にあれば例外。旧名・新名の下の searchExcludes を消し(大文字・小文字だけの変更も)、ほかのインデックス・頭が同じ名前のインデックス(Sales と Sales2)の記録は変えない。記録を消せなくても例外にしない |
IndexCatalog.RemoveIndex |
work\store\content\<名前> と取り込み一覧の記録を削除し、ほかのインデックスは残す。名前が空なら何もしない。そのインデックスの下の searchExcludes も消し、ほかのインデックスの記録は変えない。インデックスのフォルダの中にあるジャンクション、またはインデックスのフォルダそのものがジャンクションでも、先のファイルは消さない |
getSearchIndexes |
work\store\content 直下のフォルダをインデックス 1 件として返す。[インデックス管理]の一覧と同じ並びで、一覧に無いものは名前順で後ろ。元のフォルダも返す(一覧にも取り込み一覧にも無ければ、そのフォルダの元のフォルダの記録から読む。分からなければ空)。フォルダが無ければ空 |
getIndexNameMap(tests/tebunko/core/status_file) |
クロール対象フォルダの行だけを読み、見出し行の後の行・インデックス名の無い行は使わない。取り込み一覧が無ければ空 |
getIndexTsvCounts / testIndexComplete |
本文インデックスのファイルはそのファイルの相対パスで数える(0 バイトは壊れているとする)、本文インデックスのファイルがあれば元のファイルごとのフォルダが無くても「済」のまま、フォルダごとの TSV の数(TSV の無いフォルダは 0 件、大文字・小文字を区別しない、インデックス直下の TSV は数えない)、TSV がそろっていれば「済」のまま、フォルダごと削除・TSV が足りない場合は取り込み直す、0 バイトの TSV があるフォルダは壊れているとして作り直す(ほかのファイルは巻き込まない・後の TSV で数え直さない)、TSV 数が空の行・数えられなかった場合は確認しない |
publishIndexFiles |
作業フォルダの TSV をインデックスのフォルダへまとめて入れる、以前のインデックスを残さず入れ替える、TSV が 1 件も無ければ空のフォルダ、前回の出力用フォルダが残っていても入れ替えられる |
本文インデックス(tests/tebunko/index/content_index_format・tests/tebunko/search/content_index_search)
| 対象 | 主な確認内容 |
|---|---|
convertPlaceToContentIndexMeta / convertContentIndexMetaToPlace |
場所の名前(シート・ページ・スライド・非表示・ノート・図形・コメント・Excel のヘッダー・フッター・それ以外の部分)とメタ情報の往復、組み立て直して同じにならない名前はそのまま持つ |
getContentIndexFileName / readContentIndexFileName / planContentIndexParts / splitContentIndexBooksByExtension / encodeContentIndexValue / decodeContentIndexValue / convertToContentIndexBody |
本文インデックスのファイルの名前、拡張子ごとの分け方、値の %XX の往復、中身の改行を LF にそろえ U+001C〜U+001F を除く |
convertToContentIndexText / readContentIndexPlaces |
文字列にしてから読み戻すと、元のファイル・場所・中身の範囲が同じになる、Excel の図形・コメントだけ CellPrefixed が $true(Word・PowerPoint・Excel のセル・ヘッダー・フッターは $false)、版の無い・違う本文インデックスのファイルは例外 |
convertFolderToContentIndex / updateFolderContentIndex / findIndexFoldersWithBooks / publishIndexFolders |
フォルダごと・拡張子ごとに作る、元のファイルが無くなった拡張子の本文インデックスのファイルは消す、UTF-16LE(BOM 付き)で一時ファイルを残さない、置かれた TSV を入れて TSV を消し変わらない元のファイルは写す、TSV の残ったフォルダを見つけて本文インデックスに入れる、前の本文インデックスが壊れた・大きすぎるフォルダは書き換えずに飛ばして残りを書き出す(tebunko/index/content_index_store.Tests.ps1) |
searchContentIndex / getContentIndexFiles / readContentIndexLines |
結果が TSV を 1 行ずつ照合したときと同じ(改行の種類・照合のしかた・検索語ごと)、大文字・小文字・図形とコメントの除外・対象ファイル、上限・中止(ファイル 1 件ごとに止まる・作業スレッドの本体も止まる・列挙はフォルダ 1 つごとに止まる・並列で止めたあとも同じプールで検索できる)・並列・キャッシュ(書き直したら読み直す)、全文への照合の時間切れは 1 行ずつに切り替える、列挙(フォルダの一部・直下だけ・無いフォルダ)、プレビューの前後の行(キャッシュが無くても、あるときと同じ行を返す・同じファイルの続けての選択と、書き直したあとの選択で変わらない)。Excel の図形・コメントの行は、セル番地だけに一致する語(C2・2・C)ではヒットにならず、文字(文字の中のタブを含む)に一致すればヒットになる、除いた行があっても行番号がずれない、除いた行は件数・上限に数えない(1 スレッドと並列で同じ)、複数行(セル内改行)の文字は 2 行目以降の語でも一致し番地だけでは一致しない、" を含む文字は囲み・"" の形のまま照合する |
上限を超えた分の数え上げと書き溜め(tests/tebunko/search/search_overflow)
| 対象 | 主な確認内容 |
|---|---|
searchContentIndex(limit・overflowDir) |
上限(なし・1・途中・ちょうど・手前)× 検索語 × 1 スレッドと並列で、表 + 溢れのファイルを全件の順に並べると、上限なしの結果と同じ行・同じ順・同じ件数になる。HitTotal・Truncated・進行中の件数。正規表現(行頭・先読み・空行)でも同じ。上限に届かなければフォルダを作らない。書けない置き場所は OverflowFailed で表と件数はそのまま。中止は HitTotal が -1。上限を超えた後の時間切れは CountTimeout、手前なら中止 |
合図つきの照合のスクリプト(contentIndexWorkerScript) |
時間切れでも Hits を返して時間切れの欄に 1 を書く、止める印が立てば数えている最中も止まる。Excel の図形のセル番地だけの一致・長いテキストの行は表と同じ扱い |
getSearchOverflowDir / testSearchOverflowRoot / removeSearchOverflow / removeStaleSearchOverflow / readSearchOverflowLines / getSearchOverflowFiles |
置き場所の組み立て、根・途中のリンクや範囲外には作らず消さない、<PID> のフォルダを中ごと消す、起動時は動いていないプロセスのものだけを消す(今の PID・数字でない名前・ほかの PC の鍵は残す)、行の中の CR で行を分けない、作業の番号の順(head → 作業のファイル)に並べる |
invokeSearchRequest |
上限を超えると HitTotal・Truncated・溢れの置き場所を要求に入れ、次の検索で前の溢れを消す。上限なしは置き場所を決めない |
前の版との互換(tests/tebunko/indexer/index_compat・tests/tebunko/core/settings_compat・tests/meta/compat)
| 対象 | 主な確認内容 |
|---|---|
tests/tebunko/indexer/index_compat(-Tag Io) |
前の版が作った見本(前の版との互換)の本文インデックス・取り込み一覧・エクスポート zip を、今のコードで読める・検索できる・取り込み直しが起きない・エクスポートし直しても同じ配置になること |
tests/tebunko/core/settings_compat(-Tag Io) |
前の版が作った見本(前の版との互換)の setting.config が壊れたと判定されない、今のコードで同じ値として読める、書き足してもほかの値を保つこと、今の版が書く形がどれかの見本に含まれること |
tests/meta/compat(Meta) |
見本の 5 つの要素(source・ws・export.zip・file_times.tsv・expected.json)がそろっていること、今のコードの形式の目印($contentIndexVersion・$statusColumns など)が見本のどれかに残っていること。設定の見本(compat/settings/)に setting.config・expected.json がそろっていること、newSettings の全キーがどれか 1 つの見本の expected.json にあること |