きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

GradleのtestLogging

testLoggingの初期値(LogLevel別)

調べたことを表にまとめるとこうだった。

debuginfolifecyclewarnquieterror
events STARTED×××××
PASSED×××××
SKIPPED××××
FAILED ×××
STANDARD_OUT××××
STANDARD_ERROR××××
displayGranularity222222
maxGranularity-1-1-1-1-1-1
minGranularity0-1-1-1-1-1
showExceptionstruetruetruetruetruetrue
showCausestruetruetruetruetruetrue
showStackTracestruetruetruetruetruetrue
exceptionFormatFULLFULLSHORTFULLFULLFULL
stackTraceFilters ENTRY_POINT××××××
TRUNCATE×
GROOVY××××××
showStandardStreamstruetruefalsefalsefalsefalse
これを受けて、やりたいことについて少し考えてみる。

テストの開始ログが欲しい

lifecycleだとテストが動いてるのか止まっているのか良くわからない。
仮に初期値のままやるのであれば、gradle --debugとか頭おかしいことをすることになる。
では、lifecycleのeventsにSTARTEDを加えるのか?
それだとテストメソッドごとにログがでてきて憤死する。
せめて、テストクラスごとにログがでてくれればいい。
そういうときって、TestタスクのbeforeSuiteを書けばできそうだ。

test {
    beforeSuite { desc ->
        logger.lifecycle("{} {}",desc.name, tasks.testing.logging.TestLogEvent.STARTED)
    }
}

あと、gradle --infoのときはテストメソッドの開始ログ、終了ログを出してやろう。
でも、標準出力と標準エラーはごめんこうむりたい。

test {
    testLogging { 
        info {
            events "started","passed","skipped","failed"

       }
    }
}

lifecycleだけexceptionFormat=SHORTなのは嫌

test {
    exceptionFormat "FULL"
}

もしくは

test {
    testLogging {
        lifecycle {
            exceptionFormat "FULL"
        }
    }
}

テスト結果の統計を見たい

何もしなくてもTestタスク全体の統計はでるけど、テストクラス単位のも出してみるか。
そういう時はafterSuiteを使う。
ちょっと懲りすぎかもしれないがこういうコード。

test {
    afterSuite { desc, result ->
        def event = tasks.testing.logging.TestLogEvent.FAILED
        def level = logging.LogLevel.ERROR
        if( tasks.testing.TestResult.ResultType.SUCCESS == result.resultType ){
            event = tasks.testing.logging.TestLogEvent.PASSED
            level = logging.LogLevel.LIFECYCLE
        } else if ( tasks.testing.TestResult.ResultType.SKIPPED == result.resultType ){
            event = tasks.testing.logging.TestLogEvent.SKIPPED
            level  = logging.LogLevel.WARN
        }
    
        logger.log(level, "{} {} total={} passed={} skipped={} failed={} elapse={} sec", desc.className, event, result.testCount, result.successfulTestCount, result.skippedTestCount, result.failedTestCount, (result.endTime - result.startTime)/1000)
    }
}

stackTraceFiltersをかけたくない

情報がどの程度落ちてるのか分からないが、全部出させたい。フィルターを外してしまおう。

test {
    testLogging {
        lifecycle {
            stackTraceFilters=[]
        }
    }
}

gradleがテストの途中でJUnitxmlを生成してくれてないので、スタックトレースくらいは早めに見て、とっとと解析をしたい。



さすがにiPhoneで書くの辛かった。
書き間違えしてるかも。

独自クラスローダーとJacoco

これまではApache Antで、事前にJacocoのoffline instrumentationを施してからテストをしていたが、さいきんgradleに乗せ代えてon the flyになり、カバレッジが取れてない状況があった。


独自クラスローダーで、クラスを再読み込みしているテストケースでうまくカバレッジを取れていない。


迂回する方法はないかと、jacoco agentにclassdumpdirオプションを指定することにした。
gradleではclassDumpFileって書くことになるのはご愛嬌。
結果、何も出力されなかった。
お、おかしい。
何かを見落としている。


Jacocoのコードを再度確かめた。
その結果、ProtectionDomainの判定が怪しいと気付くに至った。


java.lang.instrumentパッケージのうちClassFileTransformerは、ClassLoaderのdefineClassと密接な関係がある。
defineClassの際、ProtectionDomainにCodeSourceを詰めて渡さないと、Jacoco Agentはそっぽを向いてしまうようだ。


長年の謎がやっと解けた。

ダイエット

健康診断の結果が悪いのでダイエットをしている。
2年連続で悪い。
肝臓、脂質代謝、尿酸値。
精密検査を必要とするもの2つ、そしてその上をいくのが1つ。
そろそろ年貢の納め時か。


肝臓のことは一旦忘れる。


脂質代謝、尿酸値はただの食べ過ぎだろう。
「コンビニ行くのが面倒」という理由で、ドミノピザを頼むような性格、MでもLでもXLでも食べきれる。
松屋でW定食、日高屋でW餃子定食、なか卯でカツ丼+小うどん、そんな食事をしてるから食べ過ぎてる。
BMI値は25をやや下回るくらいのほぼ肥満。


ダイエットの方針

食事制限(サークルKサンクス食材)
運動はあまりしない
高温反復浴


高いカロリーを認識する

頻繁に食べてるなー、ってものを抽出。

松屋

日高屋


W餃子定食 785kcal

なか卯


カツ丼並 861kcal

マクドナルド

フライポテトM 454kcal
フライポテトL 571kcal

ドミノピザ

チーズンロールクワトロデライトM 1248kcal


夜の食事を500kcalに抑える

近所のサークルKサンクスで頑張る。
1日1/2野菜のサラダ 56kcal
この2つがあれば迷わず選ぶ。
まずはこれで120kcal、残り380kcalは好きに使う。
例えばこいつら。
野菜100gの味噌チゲうどん 293kcal
糖質0g麺使用 担々麺スープ 231kcal
あと、余りにカロリーを抑えすぎたときには、ごまだれ豆腐そうめん 111kcalを候補に入れる。


できるだけ炭水化物は取らないが、麺類も食べてはいるのでゼロにしてるわけでもない。
尿酸値も減らすんだから肉も食べない。
あと、やたら値段が高くなるので、節約したい人にはオススメできないが、満腹感はそれなりにある。


野菜ジュースは飲まない

糖質が多い。野菜はサークルKサンクスの食材でかなりの量を取れている。ビタミン類が取れてないって印象はあるんだけど、全ての栄養素を1日分取るというのはほとんど実行困難なミッションなのでやらない。


シリアルも取らない

バランスは良いけどやっぱり糖質が多い。


運動してないならプロテインを取らない

運動したときはプロテインを取る。
コンビニでザバスミルクを探せばいい。
プロテイン中心のダイエットをするなら、牛乳500mlのうち300mlをプロテイン、200mlをシリアルに回すと使い切りやすい。


基礎代謝のことを考える

基礎代謝の平均基準値というのがあり、30代の自分は1kgあたり22.3だ。
70kgなら1561kcalになる。
目標体重は60kgとして、1338kcalだ。
運動レベルによるが、基礎代謝の1.5倍から2倍が1日に必要とされるエネルギー量になる。


脂肪1kg 7700kcal

1日1〜2食しか食べない自分の場合、
コンビニダイエット食生活では基礎代謝を賄う程度であり、
基礎代謝の0.5倍程度ずつカロリーオフされていく。
1ヶ月3kg減る勢いがあるので、3〜4ヶ月続ければ10kg減るだろう。


体重計には乗らない

人の頑張り方の違いだが、ある程度減って諦めてしまうのを避ける。
6kg減ったらもういいかって気持ちになってしまうんだよね。
腹や太腿のつまみ具合で確かめるか、継続日数からおおよその減少値を推し量る。
淡々と続ければいい。
コンビニで毎回同じものが残ってるとはかぎらないので、いかに500kcalに抑えるのかを楽しむ。多少のオーバーは多めに見る。


長風呂をする

ジョギングは億劫だ。
その代わり長めにお風呂に入る。

必需品は携帯の防水ケース。
安いものなら1000円台、高くても2000円台。

防水カバーを付けて、YouTubeを見たりする。
各種Web漫画を読むのでもいい。
高温反復浴というのもある。
生真面目にやることもないが、ちょっとお風呂から出たり入ったりするだけのことで消費カロリーが増す。


部分痩せはどうする

どうしても太腿は落ちにくい。
お風呂でマッサージでもするしかない。
あとはスクワットかな。


心拍数を測定する

脂肪燃焼させるには心拍数をあげないとダメだよね。
お風呂効果がどれほどのものか確かめる。
iPhoneアプリで心拍数を計測できるものがあるのでインストールすること。
指をカメラに当てるだけなので、厳密には脈拍を心拍数と見做しているのだろう、不整脈の人には使えないと思われる。
防水カバー越しでもまともな数値を出せてるのでお風呂でも使える。
平常時、平均70くらいの心拍数のようだった。
一番初めに計測したとき62だった、危うく徐脈かよって数字だな、過去の健康診断で脈拍58を記録したこともあるので普段から低い傾向にあるようだ。
心拍数といえばこんな本が昔あったな。

ゾウの時間 ネズミの時間―サイズの生物学 (中公新書)

ゾウの時間 ネズミの時間―サイズの生物学 (中公新書)

哺乳類の一生の心拍数は20〜23億回くらいという説。


さて、痩せる心拍数を計算する。
カルボーネン法?
目標心拍数=運動強度×(最大心拍数-安静時心拍数)+安静時心拍数
運動強度0.5として、目標心拍数は130前後。
お風呂は温度が高いほうが効果が高いらしい。
もとから42℃の風呂に入ってるので42℃にする。冬場のかなり寒いときは43℃に入ってるけど、熱いと感じるのは43℃からだなー。


初めの5分程度、湯に浸かったくらいでは、心拍数は平常時と変わらず71を示した。
では、湯船からでる。
一気に心拍数をあげるために、スクワット20回とか、軽い負荷をかける。
それから湯船に戻って心拍数の測定。
心拍数は107になった。
よし、このくらいをキープでいいや。
あとで2回測定したが、105と110になった。
110は入浴40分経過時の値。
湯船から上がったときに軽い運動を挟んでいるものの、気の抜けた正拳突きくらいで、鼻で深く呼吸して心拍数をキープする目的でやる。


高温反復浴をしても、ジョギングとかの目標心拍数を越えるようなことにはならない。
しかし、そこそこの心拍数をキープできるし、何より簡単である。疲れないわけではない。


高温反復浴って、300〜400kcalくらい消費するっていうけど、そんなことにはならない気がする。


心拍数100〜110は運動強度(METS)が約3になる。
※ここでいう運動強度(METS)は前述のカルボーネン法の運動強度とは別物です
消費カロリーは1.05×METS×運動時間(h)×体重(kg)で求められるため、20分で300kcalとか全然届かない。20分なら体重の1.05倍のカロリーを消費するくらいにしかならない。


仮に体重50kgの女子が、300kcalを20分で消費するには、運動強度(METS)が17は必要。
格闘技のMETSが10なのに、17なんて無理。
ハードルを下げて200kcalにしてみると、METSは11.3で、結局のところ無理な値だなって。


高温反復浴の数字は信じがたい数値なんだが、
METSが3ってのはウォーキング相当なので、
熱いお風呂に長く浸かることは、
ウォーキングしてるのと変わらない効果が得られることは信じられる。
心拍数が100を超えているので脂肪燃焼効果はギリギリあるというレベルで、脂肪燃焼に効率的なレベルではない。でも、やらないよりはやった方がいい。


筋肉をつけて痩せるなんて考えない

実は筋肉が1kg増えることで上がる基礎代謝はたった13kcalなんです。
たった13kcalのために筋肉つけるの?
運動を続けられる見込みがあるの?
食事制限でいいよ。
あと、基礎代謝の約40%が筋肉によるものらしいんだが、そこから平均的な筋肉量を算出してみよう。
年代別の基礎代謝量は前述のページにあり、ざっくりと30代70kgなら48kg、30代60kgなら41kg、20代60kgなら44kgくらいだ。
60kgの年齢差による筋肉量低下は約3kgで39kcalの基礎代謝低下ということになる。

だが、待て。
基礎代謝の40%は筋肉という説は古すぎるようだ。
肝臓がナンバーワンじゃねーか。
筋肉は基礎代謝の18%とある。
計算し直しだ。
30代70kgなら基礎代謝1561kcal、筋肉によるもの281kcal。30代60kgなら基礎代謝1338kcal、筋肉によるもの241kcal。20代60kgなら基礎代謝1440kcal、筋肉によるもの259kcalとなる。
筋肉なんて20kg程度しかないってこと?


なるほどの筋肉量、当たらずとも遠からず。


するってーと、肝臓を鍛えるしかない!
冒頭で一旦忘れると言った肝臓が重要。
臓器を鍛えるとかw
なかなか難しそうではあるが、肝臓に良い食事をしやがれってことだろう。
しじみ汁を買うことにする。
永谷園のやつを食べたいなー。
ふりかけならお湯を沸かさないので楽そう。


JMockitで拡張クラスローダーに読まれるクラスをモックするのを足掻いた結果

惨敗だ。


クラスローダーの委譲関係は、親が先で通常3つのクラスローダーがいる。


ブートストラップ・クラスローダー
拡張クラスローダー
アプリケーション・クラスローダー(システム・クラスローダー)


MockUp対象クラスが拡張クラスローダーにいる。
クラスを書き換えると、JMockit関連のクラスが埋め込まれるため、いざ動こうとすると拡張クラスローダーからは見付けられない。


じゃあ、拡張クラスローダーにjmockit.jarを読ませればどうなるか。
拡張クラスローダーに読み込ませるには、二つのやり方がある。
  • lib/extにjarを置く
  • -Djava.ext.dirsでディレクトリを指定
後者は、すでに使われているため、指定を増やすときは、前の値も含めて指定することに注意は払う必要がある。
仮に、jmockit.jarを含めたとしても、今度はjunitがないよ、ってなる。
junitを加えると、テストケースが見つからないよ、ってなる。
おいおい、結局全部になるんじゃねーの?


そこから考えられる方針は二つ。
  • 拡張クラスローダーに寄せる
  • アプリケーションクラスローダーに寄せる
やる気が失せるほど、どっちもどっちな予感。
ワクワクしねーから、斜め上を試す。


システムクラスローダーを作ってみる。
システムプロパティ-Djava.system.class.loaderで指定可能。
システムクラスローダーはアプリケーションクラスローダーを親に持ち、新たな委譲関係ができる。
そのため、ClassLoaderを引数に取るpublicコンストラクタが必要となる。
その先、かなりごにょごにょ。
アプリケーションクラスローダーをURLClassLoaderにキャストして、クラスパスをURL配列で掠め取り、委譲関係を辿り拡張クラスローダーを見つけて、リフレクションでそのURLをねじ込む。なんとなく雰囲気でAccessController使ってみたり。


さきほどの拡張クラスローダーに寄せるパターンを実現するための一つの方法として、ねじ込みクラスローダーが完成したわけだが、使う気にならん!


ちなみにjmockitのソースを読む限りでは、Startupを読み込んだクラスローダーがシステムクラスローダーと異なった場合、ちょっとしたことをやっているようだ。
あと、もちろんjavaagentはシステムクラスローダーによって読み込まれることは決まってる。


考えるのをやめよう。幸せは訪れない。



SQLJとFindbugs

FindBugsの除外フィルターを書いた。


その結果、300〜400個くらいのHighが消えた。


SQLJの吐き出すJavaソースはクズなので、
それに埋もれて他のソースの問題に気付きにくくなる。
迷わずに除外フィルターを書くべき。
ウォーリーを探してる場合じゃない。

gradleのTestFilterのパターンの書き方がイマイチだ

gradleのTestタスクにfilterが書ける。


このfilterに書くときのパターンの指定の解釈がうざい。
gradle/TestSelectionMatcher.java at master · gradle/gradle · GitHub


指定したパターン文字列を、アスタリスクでスプリットして、
それぞれの頭に".*"を付けてからパターンコンパイルしているように見える。
アスタリスク以外は、Pattern.quote(String)で、全部リテラルにしている。
ピリオドもリテラルになるじゃないか。
test01、test02メソッドの二つだけをヒットさせたときに、
正規表現で[1-2]みたいなのを書きたいけど解釈してくれないよね。
我慢してピリオドにしたとしてもリテラルに解釈されたら無理だよね。


二回書けということなのかよ。

test << {
  filter {
    includeTestsMatching '*test01'
    includeTestsMatching '*test02'
  }
}

どうせPattern.compileするのに、なんで正規表現を受け付けない。

Gradle MVS Dataset Plugin試作品 - Gradle Nested Container

データセットをgradleから作るようにする。


必要となるもの:
 gradle ssh plugin
 USSのtsocmdコマンド
 TSO/E ALLOCATEコマンドの知識

仕組み:
 Linux/Windowsでgradleを動かして、z/OSにsshしてtsocmdでデータセットを作成する。
 

こんな感じでbuild.gradleに書く。

datasets {
    "hlq.aaa" {
        vol = 'VOL001'
        dsorg = 'PS'
        recfm = 'F,B'
        lrecl = 80
        space = '(10,10) TRACKS'
        initialData {
            srcEncoding = '1208'
            destEncoding = '1047'
        }
    }
    "hlq.bbb" {
        vol='VOL002'
        dsorg='PO'
        members {
            "MYJCL" {
                initialData {
                    srcEncoding = '1208'
                    destEncoding = '1047'
                }
            }
        }
    }
}

もっと省略して書けるようにすることもできるだろう。
上の例では、POならDSNTYPE(PDS)やろ的な省略をしているし、lrecl未指定なら80やろ的な省略をしている。


プラグインはこんな感じで書く。

import org.gradle.api.Plugin
import org.gradle.api.Project

class DatasetPlugin implements Plugin<Project> {
    void apply(Project project) {
        def datasets = project.container(Dataset){
            String name ->
            def dataset = project.extensions.create(name,Dataset,name)
            dataset.extensions.create('initialData',InitialData,'initialData')
            dataset.extensions.members = project.container(PDSMember){
                String memberName -> 
                def member = dataset.extensions.create(memberName,PDSMember,memberName,dataset)
                member.extensions.create('initialData',InitialData,'initialData')
                member
            }
            dataset
        }
        project.extensions.datasets = datasets
    }
}

PDSMemberのところが、コンテナの中にコンテナを作ってる箇所。


Datasetクラスは、TSO/E ALLOCATEコマンドのパラメータから作成する。
Datasetクラスには、generateAllocCmd()などを定義して、sshのexecuteに渡すコマンドを組み立ててリターンするようにする。
そのコマンドは、こんな感じに組み立てる。
"ALLOCATE DATASET('hlq.aaa') VOLUME(VOL001)...."


sshセッションで使うときは、こんな感じに書ける。
execute "tsocmd \"" + datasets['hlq.aaa'].generateAllocCmd() + "\""
なんかメソッド長いから、コマンド用クラスを挟んで、datasets['hlq.aaa'].cmd.alloc()みたいにしようかな。



入れ子構造がほしかったのは、初期データを順次データセットにぶち込みたいから。
それと、PDSメンバを管理し、PDSメンバも初期データをぶち込みたいから。


初期データには、まだfileプロパティを書いていないが、イメージはある。

initialData {
    srcEncoding = '1208'
    destEncoding = '1047'
    file = sourceSets['test'].output.resources.getAsFileTree.matching{include '**/MYJCL.jcl'}.getSingleFile()
}

いったんUSSに配置して、iconvかけて、cpコマンドでデータセットに持っていく。
それをするために、エンコーディングを持たせているが、
初期値を1208,1047に設定してしまえば、より簡潔に書けることになる。


データセット作成機能:
上述のようなやり方でallocateは実現できます。


データセット初期データ機能:
iconvとcpコマンドで実現。mvでもいいかな。
おそらくInitialDataにcpコマンドのオプションをもう少し持たせる必要があるかもしれない。
また、改行コードの扱いも面倒。CRはsedで取り除くのかな。cpコマンドのオプションで、crnlとかもあったはず。


データセット存在確認:
tsocmdでLISTDSを使い、その標準出力をgrepかけて判定することで可能。


データセット削除機能:
tsocmdでFREEを使えばできる。
PDSメンバだけをFREEできるのかは不安要素、できそうなことは書いてる。


データセット圧縮機能:
んー、まだ調べ切れていない。
できる気はしていない。
PDSEにしなよって話か。


DatasetクラスとPDSMemberクラスは、descriptionプロパティをつけておく。
実はPDSMemberクラスはdescriptionプロパティしかない。。。
説明は大事。なんのためのデータセットだよ、分かんねーよ。


コードは日々改良を重ねている。PDSメンバのデータセットに初期データをぶちこむには、PDSMemberクラスのコンストラクタに、ExtensionAwareになってるDatasetクラスを渡して名前を組み立てさせるのはよさそうだ。


PDSメンバをssh経由でこねくるときは、括弧がついてるせいで、ダブルクォートで囲むなどしないといけないのが面倒臭いな。。。


このエントリーで書いてる書き方だと、すごく縦に長くなっていくから、実際には、セミコロンを使って今は横長にしてる。
書き方については、もっと工夫ができて、例えばDatasetクラスにjcllib(Map properties)を書いて、いくつかのパラメータは省略にしつつ、引数は横長にMap Expressionで書けるようにさせることができる。
ボリューム名は必須。dirやspaceは書いてほしいが、書いてなくても初期値を用意する。
ほかには、ログ出力としての置き場になってるところはspaceパラメータを多めにした簡易定義メソッドを用意してあげるとか。


recfmや、spaceの書き方を不思議に思われるかもしれないが、こっちのほうが楽にいける。


データセット名の一部を-Pで渡した値にすることもできている。システム名だとか、ホスト名だとか、ボリューム名だとか。
これができることは大事だ。


DatasetやらPDSMemberがExtensionAwareになっているのを利用して、どのテストに必要なのか印を付けられなくもない、しかし実際にはTestタスクで動くものとの連動はイメージがわかない。


ほかには、MVSデータセットと、リポジトリの資材が本当に同期しているかの確認を処理させることも考えられるかも。いや、processTestResourceなどで書き換えるので差分は出てしまうなぁ。これはやめておこう。


あー、マルチボリュームもできるのかを検証しとらんわー。ニーズは極小だから後回しかな。


まじ、やりたいことがたくさんある。