Thanks Driven Life

日々是感謝

訪問記 (オムライス編)

gongo.hatenablog.com

これのオムライス編です。7月下旬から8月下旬にかけて渡り歩きました。その記録です。順不同。

一覧

シラツユ (錦糸町)

tabelog.com

おいしい

七面鳥 (高円寺)

tabelog.com

おいしい

煉瓦亭 (銀座)

tabelog.com

おいしい。高ぇ

リスボン (浅草)

tabelog.com

おいしい

グリル満天星 (日本橋)

tabelog.com

おいしい

ヨシカミ (浅草)

www.yoshikami.co.jp

おいしい

キッチン タマヤ (森下)

tabelog.com

おいしい

YOU (銀座)

tabelog.com

二葉 (学芸大学)

tabelog.com

ランク付け難しい

オムライス、なんやかんやで店によって味はいろいろ違ってて飽きることが無くてすごかった。美味しい。

2023年8月のふりかえり

東京、8月毎日真夏日だったのうける

健康

体重の推移 筋肉、脂肪の推移
2023年8月の体重の推移。-1.8kg。 2023年8月の筋肉量と脂肪の推移。筋肉-2.8kg、脂肪+1.0kg

体重だけ見るとよくやった! だけど筋肉量が落ちているだけなので、あまりよろしくない減り方な気はする。 久しぶりに真面目にカロリー計算したりリングフィットアドベンチャー再開しているけど、なかなかうまくいかないな。

ただグラフの最後に体重が増えているのは気が抜けてドカ食いした結果なので、まあまだ希望はあるよね。

仕事

今の会社に転職して から丸6年が過ぎ、9月1日で7年目に突入しました。まだ元気です。 ちなみに前職は9年目と1、2ヶ月ぐらいだったので記録更新はまだまだ先です。

ゲーム

FF16

クリアしました! 面白かったです。 なんかまあいろいろありました*1が、概ね満足できました。

2周目はまだやっていないので勿論トロコンもまだ。気が向けばやると思います。

アーマードコア6

AC戦ムズすぎる。まだ楽しいけど。ちなみに20回以上はスッラに負けています。 あと30回ぐらい負けたら攻略動画見ようと思います。

パラノマサイト

ゲーム話というよりはポップアップストアの話。

*1:クライブ頭痛起きすぎだろみたいな

PHP 5.4 〜 PHP 8.2 をサポートする PHP パッケージの PHPUnit 環境を整えた話

モチベーション

なぜ整えるに至ったかについてです。

私はこのパッケージの作者兼メンテナーです。

github.com

(以降 本パッケージ と記載します)

PHP 5.4 で廃止された register_globalsmagic_quotes_gpcPHP 5.4 以上でも似たような形で再現してくれる君です。

つまり PHP 5.4以上で動いてほしいパッケージ であり、メンテナーとしても PHP 5.4 以上で使える状態にしておきたい のです。EOL を迎えているとしてもあえてサポートする構えなのです*1


PHP の各バージョンをサポートするにあたり、やはり自動テストは欠かせないでしょう。PHP パッケージであれば PHPUnit ですよね。 突然ですが下図をご覧ください。使用する PHP バージョン毎に、対応する PHPUnit のバージョンも異なることがわかります。

PHPUnitがサポートしているPHPのバージョン一覧絵

Supported Versions of PHPUnit – The PHP Testing Framework

これらを踏まえて、こう考えました。 ( 🔥 が本記事のメインテーマです )

  • やりたいこと
    • 本パッケージのアプリケーションコードは PHP 5.4 から PHP 8.2 (2023年8月現在) まで動く構文のみで実装している
    • ↑ を保証するために、PHP の各バージョンで自動テストを走らせたい。PHPUnit
  • やりたいことを達成するまでの懸念
    • PHPUnit はバージョン毎に(当然だが)サポートする PHP バージョンや機能があったり無かったりする
    • なるべく PHPUnit のバージョンは上げたいが、そうもいかない
      • 例:とある機能が PHPUnit 4 で動かないので PHPUnit 5 に上げたいけど、そうすると PHP 5.4 をテストできない
    • 🔥 つまりアプリケーションコードだけでなく、テストコードも 同じコードのままあらゆるバージョンのPHPUnitで動くように揃えたい
      • テストコードは PHPUnit のバージョンに合わせて複数用意する手もあるが、メンテコストがやばそうなので、最終手段としたい

本記事について

PHP 5.4 から PHP 8.2 (PHPUnit で言えば 4 から 9) までを 同じアプリケーションコード、同じテストコードのまま CI に乗せるための試行錯誤の記録となります。

期待値調整

まず PHP のバージョン差異による対応(※1)については今回主題ではないので書いていません。あくまで PHPUnit の話だけを書いています。

※1 例えば配列の分割代入で、list()じゃなくて[]使えるようになったのはPHP 7.1からだよ、とかそういうやつ


小さいパッケージなので、対応したこと自体はそこまで多くありません。そのためタイトルに書いてある状況と似た 他のパッケージすべてに適用できるような内容ではありません

?「うちのパッケージで同じことやろうとしたけど、コレも足りひんアレも足りひんで、この記事の内容だけだと全然網羅できてなかったやん!」 僕「それはそう」


本記事で書いた対応は 「これがベスト!」というものでも無い と考えています。より良い解決方法をお持ちの方は是非コメントお願いします。

対応リスト

具体的な実装は Release Release v0.0.6 · gongo/merciful-polluter · GitHub に全て含まれています。個別でもリンクを貼っておきます。

1. PHPUnit 4 → PHPUnit 6 (PHP 7.2 以降では必須)

課題

PHPUnit 6 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。

  • PHP 7.0 以上を要件とするようになった
  • PHPUnit のクラスが名前空間付きで定義されるようになった

Release Announcement for Version 6 of PHPUnit – The PHP Testing Framework

前者については書いてあるとおり、後者については例えばこういうやつです。

  • PHPUnit 6 未満
    • PHPUnit_Framework_TestCase
  • PHPUnit 6 以上
    • PHPUnit\Framework\TestCase

つまり下記のようなテストコードだと PHPUnit 7からは動かなくなる、ということです。

<?php
use \PHPUnit_Framework_TestCase;  // PHPUnit 7 だと存在しないクラス

class RequestTest extends PHPUnit_Framework_TestCase
{
}

それでは PHPUnit\Framework\TestCase に書き換えればいいじゃん! となるのですが

  • PHP 5.x は PHPUnit 5 以下でしか動かない
  • PHPUnit 5 ではまだ PHPUnit\Framework\TestCase が定義されていない

となってしまい、同じテストコードを維持したまま別々のバージョンの PHPUnit を走らせることができないのです!! 困った!!

対応

いくつか方法がありますが、早いのはこんな感じで。

  1. PHP 5.x で使う PHPUnit4.8.35 以上にする
    • PHPUnit 5系であれば 5.7.0 以上
  2. テストコードをこんな感じで書き換える
-use \PHPUnit_Framework_TestCase;
+use PHPUnit\Framework\TestCase;

-class RequestTest extends PHPUnit_Framework_TestCase
+class RequestTest extends TestCase
 {
 }

PHPUnit 4.8.35 から ForwardCompatibility/TestCase.php が用意されています。これを利用することで

  • PHPUnit 6 未満
    • PHPUnit\Framework\TestCasePHPUnit_Framework_TestCase を継承した abstract class
  • PHPUnit 6 以上
    • PHPUnit\Framework\TestCase はそのままのとおり

となります。つまり PHPUnit 6 未満でも以上でも同じテストコードで PHPUnit を実行できるということです。やったね!!

参考資料

  1. PHP5.3 対応でも PHPUnit は 6 スタイルの記述へ移行しよう - Qiita
  2. Use PHPUnit 7.5 for support PHP 7.4 by zonuexe · Pull Request #13 · gongo/merciful-polluter · GitHub
    • ForwardCompatibility が入っていない PHPUnit を使う場合は自分のリポジトリ内で同じコードを用意しよう、の例

2. PHPUnit 4 → PHPUnit 8 (PHP 8.0 以降では必須)

課題

PHPUnit 8 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。

  • PHP 7.2 以上を要件とするようになった
  • PHPUnit の一部のメソッドに戻り値の型が指定された

Release Announcement for Version 8 of PHPUnit – The PHP Testing Framework

前者については書いてあるとおり、後者については、こういうエラーが出るようになります。

PHP Fatal error:  Declaration of Gongo\MercifulPolluter\Test\BaseTest::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void

これはつまりテストコードで継承している setUp で戻り値の型が指定されていないが TestCase.setUp() では定義されているで! だから違うで! というものです。

<?php

use PHPUnit\Framework\TestCase;

class BaseTest extends TestCase
{
    // TestCase だと setUp(): void になっている
    protected function setUp() // ここは指定されていない
    {
    }
}

じゃあどうするかと考えますが、PHP 5.x だと戻り値の型指定がそもそもできないので、:void つけても syntax error で怒られていまいます。困った!!

対応

かなり妥協した案です。

Update PHPUnit (for PHP 8.0 or earlier) by gongo · Pull Request #17 · gongo/merciful-polluter · GitHub

そう、つまり 型指定されているメソッドを使わない です。正直かなり苦しい。

 class FooTest extends TestCase
 {
-    protected function setUp()
-    {
-        $this->object = new Foo;
-    }

     public function testFoo()
     {
+        $this->object = new Foo;
         $this->assertEquals('foo', $this->object->foo());
      }
 }

今回は setUp() だけで、対象となるテストメソッドもそんなに多くなかったので、ひたすら各テストメソッドの setUp() の内容を書いていくだけの作業を行いました。 これ以上の規模になってくると、テストコードを分けた方がいいんだろうなという気持ちにはなっています。

3. PHPUnit 4 → PHPUnit 9

課題

Release Announcement for Version 9 of PHPUnit – The PHP Testing Framework

今回の対象パッケージで引っ掛かったのはこちらの項目です。

Backward Incompatible Changes

The following functionality was removed:

  • Annotation(s) for expecting exceptions

アノテーションとは、今回の文脈でいうとこちらのコードです。

merciful-polluter/test/SessionTest.php at 0.0.5 · gongo/merciful-polluter · GitHub

  1. trigger_errorE_USER_WARNING を投げるコードがある
  2. PHPUnitE_USER_WARNING を例外に変換してしまう
  3. それを補足するために @expectedException アノテーションを書いておく

みたいな儀礼が存在しました。しかし PHPUnit 9 からはそのアノテーションによる補足機能を廃止することとなりました。困った!!

対応

代用としてか PHPUnit 9 から expectWarning() が誕生したので、そちらを使うようにしてみました。

<?php

    // (snip)

    /**
     * Below annotations are for PHPUnit < 9.0
     *
     * @expectedException PHPUnit_Framework_Error_Warning
     * @expectedExceptionMessage The session not yet started (Ignoring)
     */
    public function testPolluteSessionNotStarted()
    {
        // For PHPUnit >= 9.0
        if (method_exists($this, 'expectWarning')) {
            $this->expectWarning();
            $this->expectWarningMessage('The session not yet started (Ignoring)');
        }
        
        $this->object = new Session;
        $this->object->pollute();
    }

これでOK!!

参考資料

4. PHPUnit 4 → PHPUnit 10

課題

PHPUnit 10 に上げる必要性はこの時点ではなかったのですが、前項の対応を行った状態で PHPUnit を走らせると以下のような警告を貰います。

1) Gongo\MercifulPolluter\Test\SessionTest::testPolluteSessionNotStarted
Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.

PHPUnit 10 までの短い命だったのか……困った!!

対応

@expectedWarning アノテーションexpectWarning() を使わない方法、かつ PHP 5.x でも動く方法を発見しました。インターネットから。

github.com

trigger_error() が実行されそうな処理の直前で set_error_handler() をしておき、E_USER_WARNING を補足するという古き良き対策です。便利。

参考資料

結果

php5.4から8.2まですべてテストをpassしている

綺麗

まとめ

みんなちゃんと PHP バージョンアップしような! register_globalsmagic_quotes_gpc から逃れられなくて PHP 5.4 に上げられない人は、まあ頑張ってや!!

*1:使う人おらんやろという気持ちもあるので、そこまで高尚な心持ちではないです

Rubyのメソッドコードを渡すと返り値を推測する gem を作りました

github.com

ネタgemです。

Overview

こういうやつです。

WhatDyaReturn::Extractor.new.extract(<<-CODE)
  def foo
    42
  end
CODE
# => ['42']

WhatDyaReturn::Extractor.new.extract(<<-CODE)
  def foo
    if bar
      1
    else
      2
    end
  end
CODE
# => ['1', '2']
# `bar` が true か false か確定していないため、2つともありえる

できること/できないこと

確認しているシンタックスtest/what_dya_return/extractor_test.rb にずらーっと書いているので、そちらをご覧ください。

✅ early return や break はそれっぽく動いています。

WhatDyaReturn::Extractor.new.extract(<<-CODE)
  def foo
    return 42

    'baz' # unreachable
  end
CODE
# => ['42']

while とか for あたりもそれっぽく動くと思います。

WhatDyaReturn::Extractor.new.extract(<<-CODE)
  def foo
    while bar
      1
      break 2 if baz
      3
    end
  end
CODE
# => ['2', 'nil']
# break せずに while を抜ける場合は nil が返る

リテラル値が入っていることが確定している変数でも展開はしません。

WhatDyaReturn::Extractor.new.extract(<<-CODE)
  def foo
    a = 1
    a
  end
CODE
# => ['a']

❌ その他、まだ見ぬ(試していない)シンタックス。たくさんありそう。

作るに至ったモチベーション

  1. 業務や趣味のプロダクトでは RuboCop を使っている
  2. 「今更だけど RuboCop ってどうやってコードをチェックしているんだろう」
  3. rubocop から rubocop-ast に辿りつく
  4. AbstractSyntaxTreeは存在は知っていたものの、あまり深く入り込んだことがなかった
  5. なんかこれを使ってネタコード書いてみたいわ ← 今ここ

題材としては「そういえばRubyは(基本的には)最後に評価された値が返り値になるよなー」からの「じゃあ node.children.last 返すやつ書けばいいんじゃね!簡単!」ってことで、今回のテーマに決まりました。(実際にはそんなに簡単ではなかったけど)

というわけで作ってみました

当初は RubyVM::AbstractSyntaxTreeparser gem を直接使う方向で実装していたのですが

  1. パース後のツリーを辿っていく時は、ある程度抽象化された形でアクセスしたいな
    • ひたすら children[N] でやっていくのもつらいしのぉ……
    • 自分で Parser::AST::Node を拡張するという手も考えましたが、ここで次の話題に
  2. early return や if/else などの「これ以降のコードには絶対到達しない」場合は省略したいな
    • そういえば RuboCop には Lint/UnreachableCode があるやん
    • 実装真似したいけど、結構 RuboCop::AST::Node ありきの書き方が多いな
    • まるごとパクってもいいけど……
  3. ほなら RuboCop::AST に乗っかった方が早いやんけ! ← 今ここ

というわけで、実装は RuboCop::AST にがっつり依存することにしました。

結果としては上記の Lint/UnreachableCode 以外の実装も参考になったため、良い方針転換になりました。

まとめ

そこそこのパターンでそれっぽく動いたので満足しています。 まだ見ぬ未対応シンタックスについては暇潰しの1つとしてゆったり対応していこうと思います。

[おまけ1] gemの名前について

  • 「そのメソッド、何返すの?」的な役割なので、まさにそれを表現できる名前にしたかった
  • 「それってこれ?」みたいな奴で似たようなもの did_you_mean を思い浮かべた

というわけで what do you return(?)what dya return としました。

[おまけ2] 先行事例について

多分見つけられていないだけでありそう。 RBS似たようなことをやっている のも気になる(本 gem みたいにがっつり値を返すってことまではしていないはずだけど)

[おまけ3] ChatGPT

賢すぎる……

2023年7月のふりかえり

暑すぎる

健康

体重の推移 筋肉、脂肪の推移
2023年7月の体重の推移。-0.2kg。 2023年7月の筋肉量と脂肪の推移。筋肉変わらず、脂肪-0.2kg

うーむ。

運動

リングフィットアドベンチャー再開しました。

運動負荷は16ぐらい。8月中には20超えて続けたいところ。

ゲーム

FF16

引き続きプレイ中。

クライブだけ操作してて飽きるかなと思ったけど、進行度によってスタイル変えられるようになるので思ったより楽しさが続いている。ストーリーも気になる!

ウマ娘 プリティーダービー

Fate/Grand Order

プログラミング

github.com

ひさしぶりにがっつり書いた気がする。ちかいうちに感想記事を書きます。

食事

gongo.hatenablog.com

中旬以降体重増えていったの、これの影響じゃないかという気がしてならぬ

訪問記 (まぜそば、油そば編)

同僚に「東京都23区内のおすすめ油そば*1店があったら教えて」と募集したところ、数名から情報をいただいたので、5月下旬から7月上旬にかけて渡り歩きました。その記録です。順不同。

一覧

東京麺珍亭本舗

menchintei.jp

おいしい

混ぜそば みなみ

mazesobaminami.com

おいしい

武蔵野アブラ学界

www.aburagaku.com

おいしい

辛しや

tabelog.com

瞠 恵比寿店

https://ebisu-miharu.com/ebisu-miharu.com

おいしい

春日亭

www.kasugatei.com

おいしい

むぎとオリーブ

tabelog.com

おいしい

あえてランク付けするならば

前提:濃いものが好き

  • 武蔵野アブラ学界
  • 瞠 恵比寿店

*1:まぜそばでもOK」と補足。違いはよくわかっていない

2023年6月のふりかえり

暑い

健康

体重の推移 筋肉、脂肪の推移
2023年6月の体重の推移。+0.8kg。上がりが止まらない。 2023年6月の筋肉量と脂肪の推移。筋肉1.6kg、脂肪0.8kg

体重増えているけど筋肉量の影響っぽい。でも減らしたい。何故なら使いたい椅子の耐荷重が95kgだから。

ゲーム

ゼルダの伝説 ティアーズオブキングダム

シナリオクリアしました。良かった。とても良かったです。シナリオもシステムも。 まだまだミッションとか祠とか残っているので、FF16(後述)の合間に少しずつ進めています。

FF16

体験版が面白かったので本製品版も買いました。 アクション形式のFFは初めてなのですが、楽しい。 シナリオも今のところいい感じなので、しばらくは続けられそうです。

買い物

M2の15インチが発表されたタイミングで「15インチはいらないし、とはいえ MacBook Pro もそんなにな」ということで1年前のモデルを買うことにしました。今のところ満足!