たごもりすメモ

コードとかその他の話とか。

Gradle経由でのテスト実行時、コンソールに失敗したテストケースの情報を出力する

`./gradlew test`とか`./gradlew build`とかしたときに失敗したテストの情報はこの`index.html`を見てね! っていうのがめんどくさくて、なんでデフォルトで失敗したテストの情報を出してくれないんだっけ、と思っている人、主に俺の問題を解決する。
ていうかデフォルトでそうしてくれればCIサービスの設定時にテスト結果の保存とかを個別にやらないと何が失敗したかもわかんなくなってて困る、みたいな状態にならなくてすむのにね。

調べてたら「とにかくコンソールに全部出したい!」みたいなのがいっぱいひっかかるけどそうじゃないんだよなー、そういうのはtoo muchなんだよ。
で、これ。

stackoverflow.com

のうち一番下の回答(TestLoggingを使う)が簡単に調整できそうだったので、手元プロジェクトで以下のようにした。Kotlin DSL使ってるんで`build.gradle.kts`の変更。

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

// ...

tasks {
    "test"(Test::class) {
        // ...
        testLogging {
            events(TestLogEvent.FAILED, TestLogEvent.SKIPPED)
            exceptionFormat = TestExceptionFormat.FULL
            showCauses = true
            showExceptions = true
            showStackTraces = true
        }
    }
}

標準出力とか標準エラー出力とかの表示を抑制して失敗したテストケースの原因とスタックトレースを出す。やってみたらこんな表示になって自分の期待したものはこれだったんだ!!!!!!!!!!!! という感じ。

MBA:my-project tagomoris$ ./gradlew build

> Task :test

com.treasuredata.myproject.FooBarTest > should fail FAILED
    java.lang.AssertionError: expected:<1> but was:<0>
        at org.junit.Assert.fail(Assert.java:89)
        at org.junit.Assert.failNotEquals(Assert.java:835)
        at org.junit.Assert.assertEquals(Assert.java:120)
        at kotlin.test.junit.JUnitAsserter.assertEquals(JUnitSupport.kt:32)
        at kotlin.test.AssertionsKt__AssertionsKt.assertEquals(Assertions.kt:57)
        at kotlin.test.AssertionsKt.assertEquals(Unknown Source)
        at kotlin.test.AssertionsKt__AssertionsKt.assertEquals$default(Assertions.kt:56)
        at kotlin.test.AssertionsKt.assertEquals$default(Unknown Source)
        at com.treasuredata.myproject.FooBarTest.should fail(FooBarTest.kt:72)

97 tests completed, 1 failed

> Task :test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/tagomoris/td/my-project/build/reports/tests/test/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s
17 actionable tasks: 1 executed, 16 up-to-date

macOS Big Sur なM1 MacのEmacsでDocuments以下のファイル(など)を開く

M1 mac (macOS Big Sur)買ってからまだちゃんと開発とかに使えてなくてちょっとずつセットアップ進めてるんだけど、Emacsで ~/Documents 下とかが読めないことに気付いた。以前のバージョンで指摘されてた解法であるところの以下の方法もダメ:

  • M-x ns-open-file-using-panel で一度Documents下のファイルを開けばいける → ダメ
  • EmacsにFull Disk Access権限を与える → ダメ
  • 実は裏技で/usr/bin/rubyにFull Disk Access権限を与える → なんかファイル開くダイアログで/usr/binとかが開けなくなってない?(ダメ)

というわけで調べてたらこんなのが見付かった。

braveam.com
Can not do dired on ~/Documents when it is in iCloud on Catalina · Issue #84 · caldwell/build-emacs · GitHub

このままやるとx86_64なバイナリを起動しようとしてしまうので以下のように。
1. システム環境設定 → システムとセキュリティ → プライバシー → フルディスクアクセスをEmacs.appに与える
2. Terminalで以下のコマンドを実行、arm64なバイナリにリンクするよう注意

$ cd /Applications/Emacs.app/Contents/MacOS
$ mv Emacs Emacs.old
$ ln -s Emacs-arm64-11_2 Emacs

これでうまく動くようになった。めんどくさいですね。

Dropwizard ValidatorがListに対して正常に動かない

Kotlinが悪いのかListはそもそもダメなのか既知の問題なのかなどはまだ確認してない。

追記: この問題っぽい。JVMのオプション追加でどうにかなるケースと、いややっぱダメみたいな話もあるのかな。トップレベルにListを使わないのが結局のところいちばんよさそう。うーん。

Dropwizard Validator*1を使って、YAMLから読んだデータのバリデーションをしようと思ったんだけど、なんかどうやってもエラーが報告されなくておっかしいなーと思ってたところ、どうもトップレベルがListだと正常に動かないっぽい。
以下のようなテストコードを書いて確認できる。

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import io.dropwizard.jersey.validation.Validators
import javax.validation.Valid
import javax.validation.constraints.NotEmpty

val validator = Validators.newValidator()
val mapper = ObjectMapper(YAMLFactory()).registerKotlinModule()

data class TopLevel(
    @field:Valid val items: List<Item>
)

data class Item(
    @field:NotEmpty val data: String
)

class ValidationTest {
    @Test
    fun `validator does not report errors for top-level list`() {
        val yaml = """---
        |- data: ""
        |- data: ""
        |""".trimMargin()
        val list = mapper.readValue(yaml) as List<Item>
        assertNotEquals(emptySet(), validator.validate(list))
    }

    @Test
    fun `validator can report errors if top-level is not list`() {
        val yaml = """---
        |items:
        |  - data: ""
        |  - data: ""
        |""".trimMargin()
        val obj = mapper.readValue(yaml) as TopLevel
        assertNotEquals(emptySet(), validator.validate(obj))
    }
}

これでトップレベルがListのやつはエラーを報告せずfailし、トップレベルにdata classを置いてその子要素としてListを持たせたやつはバリデーションエラーを報告する。

f:id:tagomoris:20210209144401p:plain

条件を見付けるまでにだいぶ長いことハマってしまった。これ以上調べるかどうかはまだ決めてない。

*1:Hibernate由来の、Springとかでも使われてるやつ

DropwizardとShadow jarの話

AWS Lambda用にshadow jarをビルドしてたリポジトリで、別のデーモンプロセス*1をDropwizardベースにすることにして依存関係やビルド方法などを書き換えていったところ、設定ファイルに書かれてるLog appenderが見えない、みたいなエラーが出て起動に失敗するという状況に。手元で./gradlew runしたときはうまくいくんだけどサーバにデプロイしたらダメ。

io.dropwizard.configuration.ConfigurationParsingException: /opt/myapp/config.yaml has an error:
   * Failed to parse configuration at: logging.appenders.[0]; Could not resolve type id 'console' as a subtype of `io.dropwizard.logging.AppenderFactory<ch.qos.logback.classic.spi.ILoggingEvent>`: known type ids = [] (for POJO property 'appenders')
  at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.myapp.MyAppConfiguration["logging"]->io.dropwizard.logging.DefaultLoggingFactory["appenders"]->java.util.ArrayList[0])
         at io.dropwizard.configuration.ConfigurationParsingException$Builder.build(ConfigurationParsingException.java:278)
         at io.dropwizard.configuration.BaseConfigurationFactory.build(BaseConfigurationFactory.java:157)
...

なんかこんな感じのスタックトレースが出る。

調べてみたらDropwizardによる実装の探索とshadowが衝突しているっぽい。このへんのコメントを参考にした。
で、手元はGradleはKotlin DSLで書かれてるので、このコメントを参考にえいやとbuild.gradle.ktsに以下のようなのを追加。

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

// ...

tasks {
    named<ShadowJar>("shadowJar") {
        archiveBaseName.set("stream-processing-shadow")
        mergeServiceFiles()
        manifest {
            attributes(mapOf("Main-Class" to application.mainClassName))
        }
    }
}

これでうまくいった。めでたしめでたし。

*1:同じリポジトリの別Main classを使う

GradleプロジェクトでArtifactoryに依存ライブラリを置くときのKotlin DSLでの記述

なんかちょっとハマってたのでメモ。要するに公式ドキュメントのをどう書くか、だけなんだけど。

Gradle Artifactory Plugin - JFrog - JFrog Documentation

結果的にはこのissue commentを読んでやった。

// build.gradle.kts
plugins {
  // ... others
  id("com.jfrog.artifactory").version("4.17.2")
}

repositories {
    mavenCentral()
    maven {
        url = uri("https://mycompany.jfrog.io/mycompany/libs-release/")
        credentials {
            username = System.getenv("MY_ARTIFACTORY_USERNAME")
            password = System.getenv("MY_ARTIFACTORY_PASSWORD")
        }
    }
}

// snip

上記Issue commentにあった認証方式としてのBasic認証の明示は結果的には必要ないっぽい。
あと関係ないけど、上記URLの "mycompany" の部分、ふたつめを "artifactory" (設定サンプルにある)から変え損ねててずっと404になって悩んでた。

コロナ禍の最中にグランツーリスモSPORTで10kg痩せた話

TL;DR

  • 2019年11月末 73.9kg → 2020年8月末現在 64.2kg になるまで痩せた
  • 原因がグランツーリスモSPORT*1しか思い付かない
  • 食事制限などは一切なし、普段からそんなに食生活が偏ってはいないと思う、が、毎晩ビールは飲んでるぞ
  • (追記) プレイ中の心拍数が100〜140くらいで、これはいい運動になってそう

経緯

身長171cmの自分も、自転車をだいぶ強烈にやっていた頃*2には58〜60kgの体脂肪率ひと桁とかだったが、9年経った2019年年始には68kgくらいまで増量していてこれはいかんと思っていたところ、2019年1月にスキーで骨折し全く動かない生活をしばらく過ごしていたら73kgぐらいまで一気に太った。これはやばい。

とはいうものの、ライフイベントがあれこれ*3あったりもして、あまり気にすることなく危機感のない生活を送っていたら2019年のうちは体重が全然変わらなかった。んで2019年11月末に体重を計ってから体重計にも近付かずふらふら暮らしていた*4ところ、3月末の時点で気付いたらズボンがぜんぶユルくて歩いてるとずり落ちてくる。全部2019年12月末に買ったもの。おかしいなと思いつつこのときにズボンをまた買い足したんだけど、そのズボンが今現在(2020年8月)ではもうウエストが余ってベルト無しにはまったく履けない。

そうなって初めてこれはもしや痩せてるのでは、と思って自宅の体重計に乗ってみたら、いきなりものすごく痩せた数字が出てきた。びびった。65.7kg (7月上旬)。その後も特に特異値というわけではなくて、継続して体重は低下し、現在では64.2kgを記録している。

原因の考察

さて何をしたかというと、何もしていない……。もちろんコロナ禍のまっただ中なので、外に出掛けたりはほとんどしていない。運動も(自転車の新車を買ったのもあって)自宅の近所を巡ったりはたまにするが、強度が低くて話にならないのは自分で分かっている。これで痩せるなら全人類にダイエットは必要ない。

と思ったのだが、唯一思い付いたものがある。グランツーリスモSPORTだ。この2017年10月発売のゲーム、PSVRの体験のためにすぐ買ってある程度やっていたもののしばらく放置していたのを、2020年2月から再開したのだった。

www.gran-turismo.com

これ、以前にやっていたものの積み重ねから、今となってはまあまあ真剣に・そこそこのレベルで競えるようになってきた。特にコロナで出掛けられなくなってからのストレス解消に毎日1〜2時間*5修練を続け、オフラインでできる範囲のものをやり終えたからしょうがなくオンライン(スポーツモード)のレースをやりはじめたのが3月20日からであった。

もちろん、これだと思ったのには理由がある。単純に、毎回プレイするたびにめちゃくちゃ汗をかく。

f:id:tagomoris:20200826135656j:plain
ハンドルコントローラのセットアップ

こんな感じのハンドルコントローラ環境でやってるんだけど、冬のうちから、とにかく汗をかく。椅子に布がかぶせてあるのは、自分があまりに汗をかくんで妻が嫌がったからという理由による。今だと1時間レースやる*6と水を1リットルは飲む。もちろんクーラーは動かしているが。

使っているのはロジクールG29。そこまで高級なモデルでもないが、フォースフィードバックがちゃんとかかり、路面の状態もわかる(と思う)。

gaming.logicool.co.jp

やっているモードにもよると思う。オンラインでのレースやっているときの疲れかた(と汗のかきかた)は、一人でタイムアタックしてるときに較べて圧倒的に激しい。周囲のクルマの動きに気を配らないといけないし、僅かなブレーキミスが衝突に繋がるし、などなどで緊張感がぜんぜん違うからだと思う。自分のDR*7が上がったことで周囲も上級者になってきて、全体としてミスの少ないレースを強いられているのもあるはず。

というやつを、最近ではほぼ休みなく毎日1時間はやっている。これではないか。というかこれ以外に思いつかない。まあ休みなくペダル踏みつけハンドル回してるからなあ。

追記: プレイ中の心拍数を計ってみた

お昼の直後ということもあるので、他の時間帯よりも心拍数は上がりにくい状態でこのくらい、だと思う。この強度の運動が着替え・機材の準備なしにできて、かつ天候も関係なしって、ダイエット用にだいぶいい運動なんじゃないのかな。すげえ。 (追記ここまで)

そのほかの生活

食生活などはほとんど変わっていない、というかそもそも痩せたのを自覚したのがだいぶ遅かったくらいだし、まったく何もしていない。

もちろんコロナな状況なので、飲み会なんかは皆無。しかし元々そんなに高頻度で飲み会に参加していた生活でもないし、今だってたまには近所の店で外食したりもする*8。自宅での夕食時にはたいていビール飲んでるし、そのあとさらに晩酌でビールや日本酒を飲んだりもする。F1のレースがある週末なんかは330ml瓶を3本空けちゃうことだってあるし、もちろんそうなるとツマミだって用意しちゃうし。

とはいえ、普段の生活から、あまり不摂生はしてなかったなとは思う。ポテチをひと袋もりもり食べるのだって月イチくらいだし*9、ラーメンも週1くらいで食べるとはいえ大盛りにしないしスープを全部飲んだりもしない*10、晩酌のツマミもそんなにハイカロリーはものはあんまり食べてないかな、くらい。

食事は朝昼晩食べている。普通にごはんもパンも食べてる。野菜を多めにしようとはしているかもしれぬ。揚げものは自宅でやるのが面倒だからあんまり食べないなー。

というわけで

みんなもグランツーリスモSPORTやってオンラインレースで楽しくカロリー消費しようぜ!

おまけ: 最近の生活環境

前にリモートワークについてのエントリを買いてから、COVID-19はそうそう簡単に収束しないなというのが見えたのと、夫婦そろってしばらくはリモートでの仕事が確定、自分の場合はCOVID-19が収まってもかなりの割合で自宅からの勤務ができそうという話が会社から出たりもして、そんなわけで自宅で仕事をする環境を整えるため、えいやで一軒家(賃貸)に引越した。GW直前から探しはじめて5月半ばにはもう決まってた、6月半ばに引越し、という感じでだいぶスピード引越し。

間取りとしては3LDKあって仕事用の書斎がひと部屋とれたので、ワイドディスプレイやオフィスチェア、電動昇降式デスクなど一式を揃えて快適に仕事ができるようになった。扉が閉まるからZoomとかやってても他への影響が小さくて良い。

f:id:tagomoris:20200824111326j:plain
書斎のワークデスク

ひとりが書斎を使うとして、もう一人も快適に仕事するためのスペースが自宅内あちこちで取れるようにしてある。特に屋上テラスと階段下ヒキコモリスペースがお気に入り。

f:id:tagomoris:20200824111327j:plain
テラスと階段下

仕事は、もちろん、だいぶやりやすくなった。自宅内を移動するだけでも気分転換にはなるし、業務中に妻との距離がある/スペースが隔離されているのは、お互いに気をつかわなくていい。あと単純にかなり広くなったので、長期間籠もっていてもストレスが溜まりにくくなったのもあると思う。

出勤頻度の激減*11を見越して駅からはちょっと歩くけど、そこまででもない、くらい。それでもまあいいお値段の家賃になったけど、これは必要経費かなあ。最初から2〜3年の様子見のあいだだけ住む、ぐらいの感覚で決めたので許容範囲ということにしている。別の場所に行くなり買うなりはその間に社会情勢を見ながら考えるつもり。 さて、どうなるんですかねー。

*1:フォースフィードバックのあるハンドルコントローラ使用

*2:毎日夜に自宅で1.5〜2.5時間くらい自転車用のトレーニングマシンに乗っていたり、ヒルクライムレースに年5,6レースくらい出ていた

*3:結婚とか……

*4:元々コロナ前ではしょっちゅう各地の温泉施設とかに行ってそこの体重計に乗ってたので自宅の体重計に乗る習慣がなかった

*5:がっつりやるときは3時間くらい

*6:典型的には20分くらいのレースx2と10分くらいのレースx1

*7:ドライバーランク、E,D,C,B,A,A+,Sがあるなかで自分は現在B

*8:もちろん夫婦のみで、あまり密な感じの店は避けるようにしている

*9:今でもやる

*10:無料のごはんもつけない

*11:激減もなにも引越し後はまだ一度も出社してないんだけど

systemdのenvfileを普通のコマンド実行時に流用する

普段はsystemd経由で実行しているコマンドをCLIから実行したい、環境変数もsystemd経由で起動するときと同様にセットしたいのでenvfile(EnvironmentFile)をそのまま使いたいんだけどなんか微妙にやりにくくないか、と思って何度か調べたことがあるんだけど、あんまりうまい方法が検索結果に出てこない。

んだけど、あれ、これ簡単じゃん。(追記: これはごく単純なケースでしか動かなかった、後段参照)

$ env $(cat myenvfile | grep -v '^#') target-command

envfileをシェルスクリプトとして実行して追加された変数をなんとかexportすれば……みたいに考えてたけどenvコマンドで一発だった。変なコメントとか入ってると厄介だけど、こんな感じでいいのでは。ということでメモ。

空白を含む値の処理

まあいいやとスルーしてたけど値が空白を含むときに上記のコマンドだと正常に動きませんね。

$ env "$(grep -v '^#' myenvfile)" target-command

こうかな。catする意味は別になかった。

空白を含む値の処理again

上の例だとぜんぜんダメだった。ファイルのほぼ全体が最初の環境変数名にセットされて終わるという悲しい結果になる。
というか色々試してるとenvコマンドが key=value ペアのparseになんか独自のルールを持っている? っぽくって、空白文字をエスケープしようと思うとぜんぜんうまくいかない。

んで同僚の@k0kubun氏とあれこれやっていたところ彼が見付けたのが set -o allexport を使うハック。それだとちょっと他への影響が出るので、サブシェル化して実行すればいい。
このときenvfileは以下のように書かれている必要がある。

VALUE_ONE=1
VALUE_TWO="value may contain spaces"

このファイルを読み込むときに set -o allexport して全てを環境変数にする。それをサブシェルの中でやって、んでコマンドを実行する。

(set -o allexport; source envfile; target-command)

できたー!