RubyKaigi 2024 Note
ohbarye.icon 個人メモ
Principles
既存テストの並列実行/高速化の提案はしない
RSpec, cucumberの並列化なら https://github.com/briandunn/flatware
新たなテストフレームワークを作らない
rspecやminitestの既存のフローに組み込みたい
shrinkingのロジック
generatorごとに実装する必要がある
Integerにおけるshrinkの例
これ以上縮小できないbase caseを0とする
これを中立点という
縮小前の値を20とする
縮小前の値から自身を引き (20-20=0)、半分の値を引き (20-10=10)、1/4を引き (20-5=15)、1/8を引き (20-1.25=18.75)...と繰り返す
整数ジェネレーターの場合はtruncateするので18.75 -> 18でよい
すると[0, 10, 15, 18, 19, 20]のような配列が得られる
TODOs
Property based testingの理解・インプット
✅ 実践プロパティベーステスト ― PropErとErlang/Elixirではじめよう
✅ プログラミング Elixir
PBTの箇所
StreamDataを用いている
デフォルトでは100件のデータを生成
並行/並列処理に関して
論文読む
✅ 2000 QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs 読む
https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf
https://docs.google.com/document/d/1BXd3M5Y4XzBthgaWAgzGAkoN--ahdoA13ugOnZdHzE8/edit
1994 Property-based testing of privileged programs
https://ieeexplore.ieee.org/document/367311
1997 Property-based testing: a new approach to testing for assurance
https://dl.acm.org/doi/abs/10.1145/263244.263267
✅ プログラマーのためのCPU入門 ― CPUは如何にしてソフトウェアを高速に実行するか
Hacker Newsで関連の記事を漁る
https://hn.algolia.com/?q=property+based+testing
property based testingの実績調査
2000年ではQuickCheck論文の例
近年ではfast-checkで発見されたバグの実績 https://fast-check.dev/docs/introduction/track-record/
https://github.com/ohbarye/pbt の開発
✅ generatorを組み合わせられるようにする
✅ array + integer
✅ generatorを一通りつくる
https://proper-testing.github.io/apidocs/
✅ shrinkingのサポート
✅ 基礎機能
arbitraryごとの実装は必要だが呼ばれるようにできた
✅ 並列実行
shrinkのイテレーションを毎回並列実行するようにした
✅ 生成したい値が複数あるユースケースに対応
✅ generatorを可変長引数で渡せる
✅ generatorをキーワード引数で渡せる
✅ 並列化のうまみを得る方法を考える
単一のProperty based testingケースを並列実行しても高々100個のRactorにしかならない
アイデア
✅ 複数ケース(properties)を並列実行する。100個 x 100個で10,000 Ractorsを作るみたいな
❌ 複数ファイルを並列実行する
これだとparallel-testsとやっていることが同じなので新規性がない
プロセスベースでなくスレッドベースでやるのは新規性があるが...
結局test executorに手を入れないといけない。rspecやminitestの既存のフローに組み込みたい
❌ ケースの実行ではなくインプットデータの生成も並列化すべき?
やりすぎ。ruby-prop_checkのようにlazyするぐらいでいい
✅ かっこいいsyntax
毎回モジュール指定するのだるいのでElixirのPropCheckみたいな感じにしたい
fast-checkは毎回fc.って書いている https://fast-check.dev/docs/introduction/
Pbt.も短いからいいのかも
fast-checkは魔術的じゃなくて真似しやすそう
✅ 並列実行
もしかしてRactorじゃなくてもいい?parallel gemなどRactor以外を指定して使えるようにし、比較するのはあり
✅ parallel gemでプロセス、スレッドによる並列化をサポート
breakできればさらに効率的だが、同じことをractorでできない
✅ リッチレポート verbose mode
✅ ドキュメンテーション
✅ ベンチマーク
✅ RSpecなどとどうやって連携するか
itに相当するprop do ... endみたいなsyntaxが実現できるか?
新しいsyntaxをユーザーに覚えさせるのはいまいちなのでやめる
魔術によって実現はできそう
Ractorへのフィードバックまとめ
実装しながら探す
3.3.0では、例外が発生したRactorをRactor.selectに渡すとハングする
isolationに失敗したときにどのオブジェクトなのかを知りたい
Thread.killみたいに実行中のRactorを止める方法は?
キーワード引数をブロックに渡したいができない?
code:ruby
Ractor.new(x: 1, y: 2) { |x:, y:| }
# unknown keyword: :x, :y (ArgumentError)
登壇に向けて
スライド作成
発表練習
RTA in Japanを参考にするとよいらしい https://twitter.com/spikeolaf/status/1761616466858869144
日誌
2024/02/23
TODOs整理
2024/02/25
ruby-prop_checkのコード読む
lazy treeの抽象化について理解した
あらゆるところでLazyTreeでラップするのでコードが読みにくい...
generator#generateの際にshrinkingした値まで生成している
テストケースが1件でも失敗したら、そのケースの値のshrunken valuesを用いて
generatorsの結合でモナドのbind演算が出てきたところがよくわからない
array generatorを実装
2024/03/02
実践プロパティベーステスト7章
shrinking
実践プロパティベーステスト8章
実行時間が長くなる例があり、並列化の題材として良さそう
焼きなまし法や近傍探索のような話題があり、アルゴリズムの部分が難しい
2024/03/03
第3部 ステートフルプロパティテスト
モデリングが必要
ステートフルプロパティが適用できる特殊な例として有限状態機械 (Finate State Machine) のテストが挙げられていた
ohbarye.icon subscriptionのテストで使えそう
題材はcircuit breaker。Klarna製のOSSだった
2024/03/08
elixirのpbt関連実装を見た
stream dataでもlazy treeを使っていた
https://github.com/whatyouhide/stream_data/blob/main/lib/stream_data/lazy_tree.ex
そもそもlazy treeって有名なデータ構造なのか?
lazy segtreeはあるぽい
https://atcoder.github.io/ac-library/document_ja/lazysegtree.html
AtCoder LibraryのLazy Segtreeの使い方
2024/03/11
QuickCheck: A Lightweight Tool for Random Testing of Haskell Programsを読む
exhaustedの意味がわかった
実行したいケース数に対し、利用可能なテストケースがなくなってしまったとき
その場合QuickCheckは可能なケースだけ実行して成功とする
これで十分かどうかはプログラマ側で判断する
5.4でshrinkingの元ネタが出てくる
2024/03/12
論文読み切った
2024/03/14
並列度、というかRactorの生成数を増やすためにproperty構文を追加した
これでN properties x M casesのRactorができる
これはRSpecでいえばitに相当するものなので、it抜きで書ける構文を追加したい
が、それをやるとRSpec専用実装になるのであとまわし
複数のpropertyをどこかで待ち合わせてレポートする箇所が必要なのでwait_for_all_propertiesを明示的に呼ばせる必要がある...これはあまりかっこよくない
2024/03/16
ケース数などを制御するためにconfigを導入した
configオブジェクトをRactorでシェアできないつらみ
Structをセットして#configureとかで操作できるような定番な操作をしたいが....
Singleton
Configuration.instanceを呼ぶとエラー
code:ruby
can not get unshareable values from instance variables of classes/modules from non-main Ractors (Ractor::IsolationError)
クラス変数
code:ruby
can not access class variables from non-main Ractors (Ractor::IsolationError)
定数
セットするオブジェクトがsharableであればRactor内から参照はできる。frozen Hashとか
いったんこれでいく
もっと良い実装が思いついたら変えたい
2024/03/18
def property内ではRactorでないのでconfigを参照できるが...
configを明示的に引数でバケツリレーすればよい
見た目はかなりRubyishでないが...GoのContextみたいなものと思えば許せなくもない
迷うのでとりあえず定数を使う方向でいったんいく
そろそろparallel shrinkも実装していきたい
syntaxも良い感じにしたい...
2024/03/23
https://github.com/dubzzz/fast-check のコードを読んだ
チュートリアル的に書いてみたがとても優れている。並列云々抜きにこういうツールが普通にRubyにほしい
クラスが多用されておらず関数の組み合わせメインなら、構造を真似しても並列化しやすいのではと考えた
やってみたらそんなことなかった...
https://github.com/ohbarye/pbt/pull/4 にてガッと作り変えた
結果、複数のproperty based testを並列実行できなくなった
isolationの制約が厳しい...
一方、configで定数セットというトリッキーな技を使わなくてすむようにはなった
runnerやreporterを分離でき、機能を追加しやすい構造にはなったのでまずはこの方向で機能拡充する
syntaxもちょっと短くできるようにした(Pbt.から必要なものを全て呼べる
Ractor.selectがhangするときがある。既知の問題かどうかあとで調べる
次こそはshrink実装する
2024/03/24
ついにshrink実装した https://github.com/ohbarye/pbt/pull/5
shrinkの並列化について
通常のshrinkでは、失敗した値を起点としてgeneratorにさらに値を生成させる。その値のうち失敗したものを起点として...という再帰で最小値を探索する
このイテレーションの1回1回をRactorでガッと並列処理するワイルドなつくり
たぶん無駄は多いがトータルで見れば失敗値に辿り着くまでにユーザー時間は少ないという発想
streamの代替としてEnumerator
Rubyでstreamを表現するためにEnumeratorを使っている
fast-checkをみていると内部で本当にいろいろ頑張っているなという感じ
足りない機能がわかる
次はarray shrinkをもっとよくする
arrayを短くするだけでなく要素(item)もshrinkする
2024/03/25
osyoyuさんにruby/rubyのbuild方法を教えてもらって実行した
master (3b4dacf2ede0dafbcf942ac696439237f8b31dc6) でもRactorがhangする問題は起きた
./ruby -e "r = Ractor.new{1/0}; Ractor.select(*[r])"
https://bugs.ruby-lang.org/issues/20168 ぽいと思い、パッチ https://github.com/ruby/ruby/pull/9492 をあてたversionのRubyをbuildして同じコードを試したら再現しなかった
2024/03/30
tuple arbitraryで位置引数を渡せるようになった https://github.com/ohbarye/pbt/pull/6
fixed hash arbitraryでキーワード引数を渡せるようになった https://github.com/ohbarye/pbt/pull/7
arbitraryの書き方がだいぶわかってきた
RactorにわたすブロックでHashをdestructuringしたいのだけどArgumentErrorになってしまった
Ractorを使わない場合だけ許容するようなのも考えたが使い方が変わるのはイマイチなので一貫性のある挙動を採用した
2024/03/31
Ractorに渡すことになるblock内が、RSpec exampleインスタンスで実行されるかのようにできないか挑戦した
が、ダメっ...
https://github.com/ohbarye/pbt/pull/9
やったこと
block引数の中身をprism parserで文字列として取得。文字列はRactorに渡せるので渡す
渡した文字列をRSpec exampleインスタンスのinstance_evalで評価してProcインスタンス化すればいける
と思ったがRSpec exampleインスタンスをObjectSpaceから取得するところで失敗
RactorはObejectSpaceもシェアしていないので取得できない
回避するには...?
❌ RSpec exampleインスタンスをRactor内でnewする -> ブロック内で宣言されたローカル変数にはアクセスできない
code:ruby
it "passes a value that the given single arbitrary generates" do
x = 1
Pbt.assert do
Pbt.property(self, Pbt.integer) do |n|
raise unless n.is_a?(Integer)
end
end
end
🧐 itに変わるexample生成のsyntaxをつくる?
RSpec専用の実装をつくりたくない...
解決の糸口がないので後回しにする
調べたメモ
https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/instance_eval.html
pryでもソースコード抜き出せるがblockだけを抜き出すわけではないのでほしいものではなかった https://github.com/pry/pry/blob/2f8b2570f247b8cf074b6beea1aa62e6f8dd8f30/lib/pry/code.rb#L295
pryはmethod_sourceを使っているのか
https://github.com/banister/method_source/blob/81d039c966ffd95d26e12eb2e205c0eb8377f49d/lib/method_source/code_helpers.rb#L97
Prismをちょっと触れたのはよかった
node配下のコードをnode.sliceで取得できるのを見つけるのに時間かかった
自前のvisitorを書き、nodeをtree walkする過程にフックしていろいろinspectできるの面白い
autoloadが指定された定数をRactor内で初めて読み込むとエラーになる
code:ruby
RSpec.describe Pbt do
describe "arguments" do
it "passes a value that the given single arbitrary generates" do
r = Ractor.new do
loop do
v, msg = Ractor.receive
v.instance_eval msg
end
end
r.send(self, "expect(1).to eq 1")
end
end
code:ruby
# ./spec/e2e/prism_spec.rb:28:in `rescue in block (3 levels) in <top (required)>'
# ./spec/e2e/prism_spec.rb:25:in `block (3 levels) in <top (required)>'
# ./spec/e2e/prism_spec.rb:6:in `block (2 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# Ractor::UnsafeError:
# require by autoload on non-main Ractor is not supported (Eq)
# (eval at ./spec/e2e/prism_spec.rb:19):1:in `block (5 levels) in <top (required)>'
RSpec::Matchers::BuiltIn.constants.each { |c| RSpec::Matchers::BuiltIn.const_get(c) }のように自前でeager_loadすれば回避できるが... autoloadしているモジュールを全部知るのはだるい
インスタンスをsendしたあとにexpectをinstance_evalすると
どこかでclassかmoduleのインスタンス変数を触るようでIsolationErrorが起きる
code:ruby
# ./spec/e2e/prism_spec.rb:20:in `rescue in block (3 levels) in <top (required)>'
# ./spec/e2e/prism_spec.rb:17:in `block (3 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# Ractor::IsolationError:
# can not set instance variables of classes/modules by non-main Ractors
sendでcopyじゃなくてmoveするといいのかも?
と思いr.send([self, "expect(1).to eq 1"], move: true)すると
code:ruby
TypeError:
can't create instance of singleton class
ractor-tvarだと
code:ruby
require "ractor/tvar"
tv = Ractor::TVar.new(self)
code:ruby
ArgumentError:
only shareable object are allowed
既存資産がRactor readyじゃなさすぎる...!
つぎ
parallel gemなど別の並行処理ライブラリをオプションとして選べるようにしてみるテストを優先する
それらならexpectやassertionは使えるのか?もしそうならRactorよりもそっちのほうがいいということになるな...
2024/04/01 - 2024/04/02
文字列のarbitrariesを追加した
https://github.com/ohbarye/pbt/pull/10
arbitraryを組み合わせてarbitraryを作れるようになってきて面白い
arbitrary.mapでMapArbitraryを返すようにするとより汎用性が高まって良さそう
継承で複雑にはしたくない気持ちはあるが...
将来的にユーザーにも任意のgeneratorを作らせるデザインなら抽象クラスがあってもいいかも
抽象的なArbitraryでありえるもの (fast-checkにあるやつ)
MapArbitrary
別のarbitraryが生成する値をmapして使える
FilterArbitrary
別のarbitraryが生成する値をfilterして使える
ChainArbitrary
別のarbitraryが生成する値をさらに別のarbitraryに変換できる。当面はいらないかな
NoShrinkArbitrary
よくわかってないけど不要そう
NoBiasArbitrary
bias factorを無視できるやつ。今はbias factorを渡せるつくりになっていないので不要
MapArbitraryとFilterArbitraryを追加
https://github.com/ohbarye/pbt/pull/11
filterを使う場合はかなりexampleが減ってしまい、マッチする値が生成できないことがある
max_consecutive_attemptsみたいなオプションで「N回試みたが値が生成できなかったのでraiseする」というruby prop checkのような仕組みがあってもいい
https://github.com/ohbarye/ruby-prop_check/blob/843bfbfaa5ef2cf4b7119b6b7d18bc97ec04f599/lib/prop_check/generator.rb#L33-L49
2024/04/02 - 2024/04/06
毎日ちまちまと基本的なarbitrariesを追加した
https://github.com/ohbarye/pbt/pull/12
https://fast-check.dev/docs/core-blocks/arbitraries/ を参考にしつつ、基礎的なものを選んだ
parallel gemを真面目にみる
https://github.com/grosser/parallel
オプションでプロセス / スレッド / Ractorを選択できる
Ractorを選択する場合はクラスとメソッド名を固定で渡すことになる
起動したRactorに対してクラス、メソッド、引数を渡して実行させる
完了したらRactorは終了する
引数はRactor sharableなものに限る
https://github.com/vinistock/loupe と似たやり方(loupeは1 Ractorを複数テストで使い回していた)
code:ruby
# 3 Processes -> finished after 1 run
results = Parallel.map('a','b','c', in_processes: 3) { |one_letter| SomeClass.expensive_calculation(one_letter) }
# 3 Threads -> finished after 1 run
results = Parallel.map('a','b','c', in_threads: 3) { |one_letter| SomeClass.expensive_calculation(one_letter) }
# 3 Ractors -> finished after 1 run
results = Parallel.map('a','b','c', in_ractors: 3, ractor: SomeClass, :expensive_calculation)
2024/04/06
parallel gemを使ってmulti process, multi threadでも実行できるようにした
https://github.com/ohbarye/pbt/pull/13
parallel gemのRactor modeはsyntaxを大きく変えないと使えない
ユーザーが書いたブロックをdefine_methodしてやって外から呼べるようにするみたいな
モチベーション
ベンチマーカー。これで複数手段の比較がしやすくなる
expectとかのアサーションがprocess, threadなら使えるかも
Pbt.assertを並列化できるかもしれない
次こと
逐次実行の以外ケースではnum_runsが無駄に多くなっている。直せたらなおす
arrayのshrinkを賢くする
ドキュメントを充実させる
本当にRactor優位なのかベンチマークとる
expectなどがプロセス、スレッドモードなら使えるのかどうか
さらなら並列化が可能かどうか見切りをつける
リッチレポート
2024/04/07
✅ 逐次実行の以外ケースではnum_runsが無駄に多くなっている。直せたらなおす
https://github.com/ohbarye/pbt/pull/14
✅ arrayのshrinkを賢くする
https://github.com/ohbarye/pbt/pull/15
2024/04/10
RactorのCコードを読もうとしたが何もわからない
さらなら並列化が可能かどうか見切りをつける
Ractor内でrequireができないことが発覚
code:ruby
irb(main):001> Ractor.new{require 'parallel'}.take
(irb):1: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
#<Thread:0x000000011dfbaa18 run> terminated with exception (report_on_exception is true):
<internal:/Users/ohbarye/.rbenv/versions/3.3.0/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:39:in `require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from (irb):1:in `block in <top (required)>'
<internal:ractor>:711:in `take': thrown by remote Ractor. (Ractor::RemoteError)
from (irb):1:in `<main>'
from <internal:kernel>:187:in `loop'
from /Users/ohbarye/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/irb-1.11.0/exe/irb:9:in `<top (required)>'
from /Users/ohbarye/.rbenv/versions/3.3.0/bin/irb:25:in `load'
from /Users/ohbarye/.rbenv/versions/3.3.0/bin/irb:25:in `<main>'
<internal:/Users/ohbarye/.rbenv/versions/3.3.0/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:39:in `require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from (irb):1:in `block in <top (required)>'
assert自体をRactorでラップして実行するとすぐdefined with an un-shareable Proc in a different Ractor (RuntimeError)が起きる...
見切りをつけてもいいかも
2024/04/11 - 2024/04/13
expectやeqなどのマッチャーを使う方法を編み出した...が危険な匂いがすごい
procの中身を取得する
新しいクラスを定義してclass_evalでメソッドにする
定数とメソッド名ならRactorに渡せる
このクラスでexpectなどRSpec DSLが使えるようにするためinclude ::RSpec::Matchersする
Ractorは渡されたクラス名とメソッド名を用いて__send__で実行する
マッチャーのautoloadに失敗したり、クラス変数への代入が発生するところで死ぬ
発見次第prependしてオーバーライドする
main Ractorでの処理を壊さないようif Ractor.current == Ractor.main のような分岐を入れて回避する
これをやるならさらなる並列化はいったん諦めたほうがよい
Pbt.assertをnon-main Ractorでやるのはむずいので
ZeitwerkでRSpecのコードを全部ロードできるか?と思ったが、rspecのコードがZeitwerkの規約に従っているわけではなかったのでうまくいかなかった
✅ expectなどがプロセス、スレッドモードなら使えるのかどうか
使える。便利.....
✅ ドキュメンテーション
https://github.com/ohbarye/pbt/pull/17
がんばった
2024/04/14
✅ ベンチマーク
https://github.com/ohbarye/pbt/pull/17
だいたいのシナリオではworkerがいらないことがわかってしまった...
CPU-boundなシナリオでのみRactorが勝つのがわかったのはよかった
IO-boundシナリオでnoneが勝つのはなぜ?
ローカルのファイルreadぐらいじゃ大したことないからぽい
Networkリクエストを試してみた
code:ruby
require 'net/http'
require 'uri'
uri = URI.parse('http://example.com')
response = Net::HTTP.get_response(uri)
やはりprocess, threadで同じぐらいの成果でnoneだけ遅い
code:shell
$ bundle exec rake benchmark:success:io_bound
### Benchmark success:io_bound
This runs a script that does IO bound work.
ruby benchmark/success_io_bound.rb
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) arm64-darwin22
Warming up --------------------------------------
process 1.000 i/100ms
thread 1.000 i/100ms
none 1.000 i/100ms
Calculating -------------------------------------
process 0.351 (± 0.0%) i/s - 2.000 in 5.693587s
thread 0.352 (± 0.0%) i/s - 2.000 in 5.678491s
none 0.036 (± 0.0%) i/s - 1.000 in 27.668218s
Ractorで動かない問題があるので比較できず...
✅ リッチレポート
https://github.com/ohbarye/pbt/pull/19
fast-checkの実装をほぼそのまま使えた
2024/04/17
0.2.0リリース
rspecのexpectationを使える方法としてrefinementを模索していた
結論ダメ
変更できるスコープがレキシカルで狭いので、RSpec内部のコードをオーバーライドすることはできなかった
直接内部のコードを呼び出すシーンではできるけど、制御がrspecコードにわたってしまうとrefinementが無効になる
prependするパターンしか今のところ見つかってない
prependしたあとに継承ツリーから消せるんだっけ?できない気がするが要調査
2024/04/20
ファイルreadのIOベンチマークではいまいち差が出なかったので追加を試みた。が、結果はどれもイマイチ
ネットワークリクエスト
Net::HTTPによるHTTPリクエスト
Ractorではisolation errorが起きる為ベンチマークできず
TCP
ローカルホストへのTCPリクエストはRactorが早いときもあればworkerなしが早いことも...
UNIXドメインソケット
workerなしが最も早いという結果に。ファイルreadと同じようなもの
DB
sqlite3ですらRactor内では使えない...のでベンチマークできず
当初の期待通りCPUバウンドな処理でしか活躍できていない
スライドにて語りたいことをリストアップしていく
まずはモリモリで作り切る。削るのは後
2024/04/21
RSpec integrationを入れてみた
https://github.com/ohbarye/pbt/pull/20
RUBY_MN_THREADS=1でベンチマークを回す
https://github.com/ohbarye/pbt/pull/21
0.3.0リリース
スライド作成
とりあえず字だけで50ページぐらい一気に書き殴った
話したいことを概ね詰め込んだ
これでざっくり喋ってみてフォーカスすべき点を決めるのはありかも
いらないところも削らずにスライドには残しておく
スライド提出まであと15日。休日はあと7日... 計画していかないとやばい
2024/05/05
ベンチマーク更新
https://docs.google.com/spreadsheets/d/1EgFRIH4DWQ71BFRU_YTvpEoUn3fvvlhsbZsgqu1jZ1k/edit#gid=0
~2024/05/06
ひたすらスライドづくり。96ページ
通し練習してスキップする対象を決めたら提出する
2024/05/07
デモコード実装
https://github.com/ohbarye/rubykaigi-2024-demo
2024/05/16
いろんな人からフィードバックもらったり議論できた
ffu
HaskellだとQuickCheckがテストツールのfirst choiceになるのでけっこうPBTに馴染みがある
一方、何やっているのか初見でわからないテストも多い
そもそもテストエコシステムが充実していないという課題があった。今はマシかも
ohbarye.icon 補完的な関係にあるExample basedとProperty basedのうち、Haskellは後者に偏っている。Rubyではほぼ前者なので文化圏によって偏りがあるのは面白い
hsbt
Ractor対応させる系のPRは実はそれなりに来ている。10件ぐらいは見た
実行速度の問題があったりするがパッチに問題なければマージしている
構造的な変更を伴うものもある
Ruby本体が使っているtest-unitではテストでRactor対応しているかどうかを判別できるようにしている
RubyKaigi Takeout 2021とtest-unit - Ractor対応とdebug.rb対応とCoreAssertionsの置き換え #rubykaigi
def宣言の前にractorと書く
Ruby本体のコードではこの機能ではなくassert_ractorを使っている
どっちかに寄せていくべき、どっちがいいか、という議論がここ最近起きていた
内部的には違うことをやっている気がするがまだよくわかってないのでコードを見てみると良さそう
ko1
Thread safetyについて
今でこそいろいろなgemがスレッドセーフということになっているが確実にセーフである保証はしてない
見つかったバグが修正されているにすぎない
STMを入れたかった
ujm) 問題が難しくなるのでは?
DBへの書き込みとかみんなやっているし理解できるはず
forkは意外と遅くない
Copy on writeで生成コストが低くできる
mtsmfm
Ractorを使うようなコードをユーザーが書くのではなく、キラーアプリやツールみたいなのが出てほしさ
ProcessやThreadがユーザーに露出することはあまりない
Links
Repository https://github.com/ohbarye/pbt
Proposal https://hackmd.io/8MDSw2n-RBKJxOgJBxo9eg?view
M:Nスレッドによる軽量な並行処理への挑戦
sofrware transaction memoryが必要なら使う
https://github.com/ko1/ractor-tvar
実例
LLMのテスト
https://towardsdatascience.com/testing-large-language-models-like-we-test-software-92745d28a359
https://martinfowler.com/articles/engineering-practices-llm.html
This is where the unit testing paradigm, which depends primarily on equality assertions, reaches its limits when dealing with varied responses from an LLM.
創造性の高いLLMアプリケーションからの出力を、等価アサーションでテストするのは困難
対象の特性を満たすことを検証する
RBSをテストコードにする
ksssさんによる、RBSの情報をもとに任意の値でテストを呼び出すツール
他言語
Haskell
QuickCheck
https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf
Clojure
https://github.com/clojure/test.check
Elixir
StreamData
https://hexdocs.pm/stream_data/ExUnitProperties.html
PropEr
Quixir
プログラミングElixirの著者が書いたやつ
https://github.com/pragdave/quixir
outdatedだがコードがものすごく少ない
generator (stream data生成) は https://github.com/pragdave/pollution で実装されている
ExCheck
https://github.com/parroty/excheck
outdated
Erlang
PropCheck
JavaScript / TypeScript
https://github.com/dubzzz/fast-check