Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
時の止め方を考える
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
tenntenn - Takuya Ueda
PRO
October 23, 2024
Technology
130
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
時の止め方を考える
Go Connect#3で発表した資料です。
https://gotalk.connpass.com/event/331992/
tenntenn - Takuya Ueda
PRO
October 23, 2024
More Decks by tenntenn - Takuya Ueda
See All by tenntenn - Takuya Ueda
(続)生成AIによる 静的解析ツールの自動生成
tenntenn
PRO
0
82
まずはイテレータ(range over func)の仕様を学ぼう
tenntenn
PRO
0
480
モチベーションの維持と世代交代から考える持続可能なコミュニティ運営
tenntenn
PRO
0
180
fmfm🤔 fmtパッケージ - tenntenn.go#4
tenntenn
PRO
0
240
あなたの知らない go listコマンド - tenntenn.go#4
tenntenn
PRO
1
1.5k
ライブペアプログラミング その2 - tenntenn.go#3
tenntenn
PRO
0
99
sync.Onceを完全に理解する - tenntenn.go#3
tenntenn
PRO
0
590
Go 1.18で追加されるstrings/bytes.Cutと(*sync.Mutex).TryLockについて - Go1.18リリースパーティ
tenntenn
PRO
0
690
io/fsパッケージを読む - tenntenn.go#2
tenntenn
PRO
0
280
Other Decks in Technology
See All in Technology
どこまでAIに任せるか 〜確率論と決定論の境界決定〜
shukob
0
560
最高のシステムプロンプトを作るためにフィードバック機能を導入した話
alchemy1115
0
220
AI驚き屋発見器
yama3133
1
380
なぜ、あなたのAPIは使われないのか? AX時代の設計原則、ガードレール、運用体制
yokawasa
1
260
コンポーネント名には何を含めるべきなのか? / what-should-be-included-in-component-names
airrnot1106
0
170
現場との対話から始める “作る前に問い直す”業務改善
mochico50
2
330
終末スローライフアプリ『LIVING with LIVLIES』運用を支える技術~不思議な生き物リヴリーの体を借りて、終末世界を冒険!世界観を支えるURP描画設計・量産パイプライン・AI活用について~
202409e2
0
110
大 AI 時代におけるC# の事情 ~ぶっちゃけトークを交えながら~
nenonaninu
1
520
AI研修(Day1)【MIXI 26新卒技術研修】
mixi_engineers
PRO
2
2.6k
『モンスターストライク』 の運営に伴走する! データ民主化への 解析グループの3つのアプローチ
mixi_engineers
PRO
0
170
NYC Summit 2026 におけるAmazon Bedrock AgentCore のアップデート
ren8k
1
260
全社でのソフトウェアサプライチェーン攻撃対策をやってみた with Takumi Guard
z63d
0
320
Featured
See All Featured
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.1k
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
230
Skip the Path - Find Your Career Trail
mkilby
1
170
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
270
The Curse of the Amulet
leimatthew05
2
13k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
400
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Context Engineering - Making Every Token Count
addyosmani
9
1k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
230
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.5k
Transcript
The Go gopher was designed by Renée French. The gopher
stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. 時の止め方を考える 2024/10/22 Go Connect #3 https://tenn.in/ctxtime
UEDA Takuya / tenntenn 上田拓 也 newmo株式会社 / ソフトウェアエンジニア 一般社団法人
Gophers Japan 代表理事 Google Developers Expert (GDE) / Go Category tenntenn Conference 主催・登壇者 2013年よりGo Conferenceの運営を行う。 2016年、メルカリグループに入社、Goコミュニティへの貢献や採用・社内教育などに従事。 2021年、一般社団法人Gophers Japan設立、代表理事に就任。Google Developers Expert (GDE)に選出。 2022年、株式会社ナレッジワーク入社。ソフトウェアエンジニアおよびGoエンジニアのイネーブルメントに従事。 2024年、newmo株式会社入社。ソフトウェアエンジニアとしてプロダクト開発に従事。 略歴
func Now(context.Context) time.Time { return time.Now() } compilable compile error
1 2 Q
時を止めるとは? ▪ 時刻を使ったコードのテストがしたい • 特定の時刻で固定する package greet func Greet() string
{ now := time.Now() switch h := now.Hour() { case h >= 4 && h <= 9: return "おはよう" case h >= 10 && h <= 16: return "こんにちは" default: return "こんばんは" } }
よくある時の止め方 ▪ 引数に現在時刻を渡す ▪ 関数やインタフェースで差し替える • パッケージ変数やフィールドで持つ ▪ コンテキストに現在時刻を設定する •
context.WithValue関数を使う
やんちゃな時の止め方 - testtime func Now() $GOROOT/src/time/time.go testtime 静的解析 func Now()
$GOPATH/testtime/xxxx/time.go 書き換え go test -overlay 利用 参考:https://tenntenn.dev/ja/posts/2021-07-06-testtime/
静的解析による time.Now関数さがし ▪ GOROOTを取得する • go env GOROOTコマンドを実行 • runtime.GOROOT関数ではコンパイル時のGOROOTになりダメ
▪ パッケージの情報を取得 • (*build.Context).Importメソッドで取得できる • Context.GOROOTフィールドを変更しておく必要がある • パッケージがあるディレクトリのパスを取得 ▪ timeパッケージを構文解析する • parser.ParseDir関数でパースしてAST(抽象構文木を取得) ▪ ソースコードを変更する • ASTを変更する • x/tools/go/astutil.AddImport関数をimport宣言を追加する • go/format.Node関数で出力する
-overlayオプション ▪ ビルド時にファイルを変更する • Overlay JSONのパスを指定する • パスを読み替えることができる ▪ testtimeの実行結果を指定する
• 作成したOverlay JSONのパスをstdoutに表示する • go test -overlay $(testtime)で入れ替えることができる { "Replace": { "/usr/local/go/src/time/time.go": "/Users/tenntenn/go/pkg/testtime/time_go1.16.go" } }
//go:linkname ▪ リンカでシンボルを読み替える • 外部パッケージのシンボル(識別子)を参照できる • Go1.23からstdへのlinknameは禁止になった ◦ ハンドシェイクしてあればOK ▪
自作自演でハンドシェイク • -overlayで上書きするのでハンドシェイクを加える ▪ 共通のsync.Mapを置く • timeパッケージに宣言を追加 • testtimeパッケージがlinknameしておく
ゴルーチンの ID ▪ スタックトレースを自力でパースして取得する • いつ変更されるか分からないので真似してはダメ func goroutineID() string {
var buf [64]byte n := runtime.Stack(buf[:], false) // 10: len("goroutine ") for i := 10; i < n; i++ { if buf[i] == ' ' { return string(buf[10:i]) } } return "" }
testtimeの使い所 ▪ すでにそれなりの規模のプロジェクト • 散らばったtime.Now()を止められない ▪ やんちゃさを暖かく見守れる心 • -overlayとlinknameとゴルーチンのIDを使っている •
テストだし、まぁ最悪いいかの心 • testtime.Overlay()で-overlayしてるか取得できる ◦ 普段はt.Skipで飛ばしてほしい
まじめな時の止め方 - ctxtime ▪ コンテキストに現在時刻を設定する • テストの時だけNow関数を入れ替える ctxtime.Now internal.Now ctxtimetest
呼び出し internal.DefaultNow 呼び出し 入れ替え ctxtimetest.nowForTest 呼び出し 参考:https://tech.newmo.me/entry/2024/09/20/133402
Contextがあれば後でどうにでもなる ▪ newmo入社時に見つけたコード • よくわかってる人(yuki-ito)が書いたと思われるコード ◦ あとで困ることを経験上知っている func Now(_ context.Context)
time.Time { return time.Now().In(time.UTC) }
テストのときだけ挙動を変える ▪ internalを使った3pkg方式 • ctxtimeパッケージはtestingパッケージに依存したくない • internalパッケージに実装を置いておき変更可能にしておく ◦ パッケージ関数ではなく変数にしておく •
ctxtimetestパッケージをimportするとinit関数で入れ替える ◦ testing.Testing関数でテストか判定できる ctxtime.Now internal.Now ctxtimetest 呼び出し internal.DefaultNow 呼び出し 入れ替え ctxtimetest.nowForTest 呼び出し
testid ▪ テストに個別のIDをふる • https://github.com/newmo-oss/testid • (*testing.T).Nameメソッドでテスト名は取得できる ◦ 子テストも含めて •
テスト関数より多くIDが振りたくなることを見越して作成 ▪ コンテキストに仕込む • ミドルウェアやインタセプターで仕込む • ログやらいろんなところでテストが識別できて便利
Linterなきルールはルールではない ▪ time.Now関数を呼び出している箇所を見つける func run(pass *analysis.Pass) (any, error) { in
:= pass.ResultOf[ssainspect.Analyzer].(*ssainspect.Inspector) timenow, _ := analysisutil.ObjectOf(pass, "time", "Now").(*types.Func) if timenow == nil { return nil, nil } for in.Next() { c := in.Cursor() if analysisutil.Called(c.Instr, nil, timenow) { pass.Reportf(c.Instr.Pos(), "do not use %s, use ctxtime.Now", timenow.FullName()) } } return nil, nil }
まとめ ▪ 時を止めたい • テストで時を固定したい ▪ 最初からちゃんと考慮する • コンテキストがあればどうにかなる!
宣伝① まだ資料つくってないので、時を止めたいッッ
宣伝② @newmotech
宣伝③ tenn.in/cfc25