SSDに優しくなりたい
行単位でstage/unstageできるgit GUIクライアントを作っています。
週刊 git GUIクライアントを作る [1] stage/unstage基礎知識編
週刊 git GUIクライアントを作る [2] stage/unstage不完全攻略編
gitを使うアプリケーションなので、そのテストのためにファイルの読み書きが発生します。
コードに対する不安があり、そしてテストコードを書くのが初めてで楽しくて調子に乗ってるため、任意の状況での行の選択パターンを網羅したテストというのを行っていて、現時点では70秒ぐらいかかります。
70秒もずっとファイル読み書きするテストをコードの変更のたびにやっていると「SSDに優しくないな」という気持ちになりました。ビルドもテストも使い捨てるものなので、メモリ上でやれたら嬉しいですね。ちょっと調べてみると、以下の方法で良さそうなので試してみました。
方法
- RAM Diskを作る
- ビルドの出力先をRAM Diskに変更する
対象環境
- Windows
- Gradleプロジェクト
今回使うのはImDiskとgradleだけなので、これ以外に制限は無いはずです。
他の環境でも同様のことは可能だろうと思います。
IOを伴うテストについて
ほかにいい方法があるのかもしれませんが、
現在はgradleプロジェクトでJunit5を使って次のようにテストしています。
- src\test\resources\以下に、操作対象とするファイルを用意する
- それがコピーされたbuild\resources\test以下で、ファイルとgitの操作を行う
- テストリソースとしての.gitフォルダを持ちたくないので、毎回git initから行う
このため、ビルドの出力先をメモリ上に持っていくだけで、目的が達成されます。
なお、実用性は求めていませんが、70秒のテストが60秒ぐらいになりました。
RAM Diskを作る
ImDisk Virtual Disk Driverというのを使いました。
GUI版のImDisk Toolkitもいい感じでした。
参考記事
http://siwon-g.hateblo.jp/entry/20151122/1448195973
http://jinblog.at.webry.info/201402/article_2.html
ビルドの出力先を変更する
- build.graldeで
project.buildDir
変数を変更します。 - ソースと異なるドライブでも、万事うまくいく雰囲気です。
build.graldeにまとめる
- 指定のドライブがマウントされているかどうか確認する
- JavaのFile.listRoots()でドライブの一覧を取得できます
- マウントされていなければRAM Diskを作ってマウントする
- 例えば
'imdisk -a -s 2g -t vm -o rem -p "/fs:ntfs /q /y" -m ' + G:
-
-s 2g
オプションでサイズ指定します。-s -2g
のように負の値を指定して、2gを残してそれ以外をめいっぱい使う、ということもできるようです。。 -
-o rem
オプションでリムーバブルドライブにしています。そうでない場合、管理者権限が必要とされたり、強制アンマウントが必要になったりします。 -
-p "/fs:ntfs /q /y"
オプションでファイルをフォーマットできます。なお、GUIクライアントのImDisk ToolkitでRAMディスクを作ってマウントすると、Windowsがフォーマットするかどうか尋ねてきてくれます。 -
-m G:
オプションでマウント先を指定します。試してませんが、-m #:
と書くと、空いてるドライブ文字を選択してくれます。
def TARGET_DRIVE = 'G:'
def dir = TARGET_DRIVE + '\\project_name\\build'
if (hasDrive(TARGET_DRIVE)) {
buildDir = dir
} else {
def COMMAND = 'imdisk -a -s 2g -t vm -o rem -p "/fs:ntfs /q /y" -m ' + TARGET_DRIVE
println "Mounting RAM Disk to $TARGET_DRIVE is ..."
def ret = COMMAND.execute().waitFor()
if (ret == 0 && hasDrive(TARGET_DRIVE)) {
println "done by command: "
buildDir = dir
} else {
println 'failed by command: '
}
println ' ' + COMMAND
}
println 'buildDir: ' + buildDir
// 指定のドライブがマウントされているか
static boolean hasDrive(String letter) {
def drive = File.listRoots().find {
return it.absolutePath.startsWith(letter)
}
return drive != null && drive.exists()
}
ロジックはリモートにpushしてもいいかもしれませんが、ドライブ文字の選択は個人でも環境によって異なるので、そのままだと微妙かもしれません。少しだけ考えてみると、次のような案が思いつきました。
-
.gitignore
したローカルファイルにドライブ文字を書けるようにする -
#:
を使って、ビルドのたびに作成・削除する
(追記)設定ファイルを利用する
.gitignore
したローカルファイルにドライブ文字を書けるようにする
上述の案を実際に書いてみました。
config.groovyに設定を書きます。
-
RAM.drive
に書かれたドライブ文字を利用してRAM diskを作成 -
RAM.drive
が無ければ何もせず、通常のbuildDirを利用 -
RAM.size
が設定されていばそのサイズを指定、無ければデフォルトのサイズ1GBでRAM diskを作成 -
RAM.size
が設定されていばその形式でフォーマット、無ければデフォルトのntfsでフォーマット
RAM {
drive = 'G:'
size = '2g'
}
config.groovy
def config = new ConfigSlurper().parse(file('config.groovy').text)
def TARGET_DRIVE = config.RAM.drive ?: ''
if (TARGET_DRIVE) {
def dir = TARGET_DRIVE + '\\project_name\\build'
if (hasDrive(TARGET_DRIVE)) {
buildDir = dir
} else {
def size = config.RAM.size ?: "1g"
def format = config.RAM.format ?: "ntfs"
def COMMAND = "imdisk -a -s $size -t vm -o rem -p \"/fs:$format /q /y\" -m " + TARGET_DRIVE
println "Mounting RAM Disk to $TARGET_DRIVE is ..."
def ret = COMMAND.execute().waitFor()
if (ret == 0 && hasDrive(TARGET_DRIVE)) {
println "done by command: "
buildDir = dir
} else {
println 'failed by command: '
}
println ' ' + COMMAND
}
}
println 'buildDir: ' + buildDir
groovyはエルビス演算子?:
が使えて気持ちいいですね。